<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet title="XSL formatting" type="text/xsl" href="http://blog.pagesd.info/feed/rss2/xslt" ?><rss version="2.0"
  xmlns:dc="http://purl.org/dc/elements/1.1/"
  xmlns:wfw="http://wellformedweb.org/CommentAPI/"
  xmlns:content="http://purl.org/rss/1.0/modules/content/"
  xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
  <title>blog.pagesd.info</title>
  <link>http://blog.pagesd.info/</link>
  <atom:link href="http://blog.pagesd.info:82/feed/rss2" rel="self" type="application/rss+xml"/>
  <description>Seconde tentative pour regrouper toutes mes notes de développement qui jusqu'ici étaient dispersées un peu de partout (dans le meilleur des cas) voire consignées sur des bouts de papiers avant de disparaitre.</description>
  <language>fr</language>
  <pubDate>Tue, 10 Jan 2012 18:40:38 +0100</pubDate>
  <copyright></copyright>
  <docs>http://blogs.law.harvard.edu/tech/rss</docs>
  <generator>Dotclear</generator>
  
    
  <item>
    <title>Configurer ELMAH pour utiliser SQL Server</title>
    <link>http://blog.pagesd.info/post/2011/12/23/configurer-elmah-pour-utiliser-sql-server</link>
    <guid isPermaLink="false">urn:md5:a96b7253613b7f88cdc3e491d1a350e2</guid>
    <pubDate>Fri, 23 Dec 2011 13:27:00 +0100</pubDate>
    <dc:creator>Michel</dc:creator>
        <category>Elmah</category><category>NuGet</category>    
    <description>    &lt;p&gt;Pour avancer un peu plus dans mon &lt;a href=&quot;http://blog.pagesd.info/post/2011/09/28/installer-elmah-avec-nuget&quot; hreflang=&quot;fr&quot;&gt;utilisation d'ELMAH&lt;/a&gt;, j'ai cherché à enregistrer les informations qu'il
collecte dans une base de données.&lt;/p&gt;
&lt;p&gt;Pour cela, j'ai réalisé quelques essais pour voir comment faire et pour
comprendre comment cela fonctionnait :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Configuration automatique de SQL Server Compact&lt;/li&gt;
&lt;li&gt;Configuration semi-automatique SQL Server&lt;/li&gt;
&lt;li&gt;Configuration manuelle de SQL Server Compact&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;La suite de ce billet récapitule le fonctionnement propre à chacune de ces
méthodes.&lt;/p&gt;
&lt;h3&gt;ELMAH + Sql Server Compact en automatique&lt;/h3&gt;
&lt;p&gt;Sous NuGet, je recherche &amp;quot;elmah&amp;quot; puis choisis le package &amp;quot;ELMAH on MS SQL
Server Compact&amp;quot;. Sa description indique qu'il s'agit d'une configuration pour
démarrer rapidement avec une base de données Microsoft SQL Server Compact.&lt;/p&gt;
&lt;p&gt;Ca semble être un meilleur choix que &amp;quot;ELMAH on MS SQL Server (requires
manual config)&amp;quot; puisque à priori, l'absence de &amp;quot;requires manual config&amp;quot; devant
logiquement signifier que je n'aurai rien à faire.&lt;/p&gt;
&lt;p&gt;Je clique sur le bouton [Install] et l'installation débute, mais impose
l'installation de Sql Server Compact 4.0.8+. Je tique un peu parce qu'il me
semblait que j'étais à jour ? Mais bon, [I accept] et le projet référence
maintenant 2 packages supplémentaires :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ELMAH on MS SQL Server Compact&lt;/li&gt;
&lt;li&gt;SqlServerCompact&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Il suffit alors de relancer l'application et de se rendre sur l'URL
elmah.axd pour confirmer que tout a été configuré sans que effectivement j'ai
eu quoi que ce soit à faire : &lt;code&gt;&amp;quot;This log is provided by the SQL
Server Compact Error Log&amp;quot;&lt;/code&gt;. Génial !&lt;/p&gt;
&lt;p&gt;Si je regarde dans le dossier &amp;quot;App_Data&amp;quot;, je constate qu'il contient
désormais une base de données &amp;quot;Elmah.sdf&amp;quot;. Un double-clic pour l'ouvrir et je
peux voir que celle-ci est constitué d'une seule table &amp;quot;ELMAH_Error&amp;quot;, avec les
colonnes nécessaire pour enregistrer tout le détail des erreurs :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ErrorId&lt;/li&gt;
&lt;li&gt;Application&lt;/li&gt;
&lt;li&gt;Host&lt;/li&gt;
&lt;li&gt;Type&lt;/li&gt;
&lt;li&gt;Source&lt;/li&gt;
&lt;li&gt;Message&lt;/li&gt;
&lt;li&gt;User&lt;/li&gt;
&lt;li&gt;StatusCode&lt;/li&gt;
&lt;li&gt;TimeUtc&lt;/li&gt;
&lt;li&gt;Sequence&lt;/li&gt;
&lt;li&gt;AllXml&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Et si maintenant j'étudie ce qui a changé dans l'application, je m'aperçois
qu'en fait c'est trois fois rien dans le web.config :&lt;/p&gt;
&lt;p&gt;Un :&lt;/p&gt;
&lt;pre&gt;
&amp;lt;connectionStrings&amp;gt;
  ...
  &amp;lt;add name=&amp;quot;elmah-sqlservercompact&amp;quot;
       connectionString=&amp;quot;Data Source=|DataDirectory|\Elmah.sdf&amp;quot; /&amp;gt;
&amp;lt;/connectionStrings&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Deux :&lt;/p&gt;
&lt;pre&gt;
&amp;lt;system.data&amp;gt;
  &amp;lt;DbProviderFactories&amp;gt;
    &amp;lt;remove invariant=&amp;quot;System.Data.SqlServerCe.4.0&amp;quot; /&amp;gt;
    &amp;lt;add name=&amp;quot;Microsoft SQL Server Compact Data Provider 4.0&amp;quot;
         invariant=&amp;quot;System.Data.SqlServerCe.4.0&amp;quot;
         description=&amp;quot;.NET Framework Data Provider for Microsoft SQL Server Compact&amp;quot; type=&amp;quot;System.Data.SqlServerCe.SqlCeProviderFactory, System.Data.SqlServerCe, Version=4.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91&amp;quot; /&amp;gt;
  &amp;lt;/DbProviderFactories&amp;gt;
&amp;lt;/system.data&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Et trois :&lt;/p&gt;
&lt;pre&gt;
&amp;lt;elmah&amp;gt;
  &amp;lt;errorLog type=&amp;quot;Elmah.SqlServerCompactErrorLog, Elmah&amp;quot;
            connectionStringName=&amp;quot;elmah-sqlservercompact&amp;quot; /&amp;gt;
&amp;lt;/elmah&amp;gt;
&lt;/pre&gt;
&lt;h3&gt;Rollback&lt;/h3&gt;
&lt;p&gt;Je désinstalle le package &amp;quot;ELMAH on MS SQL Server Compact&amp;quot; (mais pas les
packages dont il dépend) puis le package &amp;quot;SqlServerCompact&amp;quot;.&lt;/p&gt;
&lt;p&gt;Puis je retourne sur elmah.axd où je peux constater que je suis bien revenu
à mon point de départ : &lt;code&gt;&amp;quot;This log is provided by the In-Memory Error
Log.&amp;quot;&lt;/code&gt;. Parfait !&lt;/p&gt;
&lt;h3&gt;ELMAH + Sql Server en semi-automatique&lt;/h3&gt;
&lt;p&gt;Ce coup-ci je fais un essai avec l'installation du package NuGet &amp;quot;ELMAH on
MS SQL Server (requires manual config)&amp;quot;.&lt;/p&gt;
&lt;p&gt;Une fois terminé, un nouveau dossier &amp;quot;App_Readme&amp;quot; est apparu dans le projet,
avec deux fichiers : - Elmah.SqlServer.sql - Elmah.SqlServer.txt&lt;/p&gt;
&lt;p&gt;Et le fichier Elmah.SqlServer.txt contient le texte suivant :&lt;/p&gt;
&lt;pre&gt;
Please note that in order to complete the installation of ELMAH.SqlServer you will have to do the following:

1) Run the Elmah.SqlServer.sql script against your database
2) Edit your web.config with the correct settings in the elmah &amp;lt;connectionString&amp;gt; to connect to your database
&lt;/pre&gt;
&lt;p&gt;Malgré tout, le fichier web.config a déjà été pas mal préparé pour
simplifier la configuration :&lt;/p&gt;
&lt;p&gt;Un :&lt;/p&gt;
&lt;pre&gt;
&amp;lt;connectionStrings&amp;gt;
  ...
  &amp;lt;add name=&amp;quot;elmah-sqlserver&amp;quot; 
       connectionString=&amp;quot;Data Source=****;User ID=****;Password=****;Initial Catalog=****;&amp;quot;
       providerName=&amp;quot;System.Data.SqlClient&amp;quot; /&amp;gt;
&amp;lt;/connectionStrings&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Et deux :&lt;/p&gt;
&lt;pre&gt;
&amp;lt;elmah&amp;gt;
  &amp;lt;errorLog type=&amp;quot;Elmah.SqlErrorLog, Elmah&amp;quot; 
            connectionStringName=&amp;quot;elmah-sqlserver&amp;quot; /&amp;gt;
&amp;lt;/elmah&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Je sais jamais faire, mais sinon, c'est pas compliqué :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;démarrer SQL Server Management Studio (l'occasion de voir que j'ai des
tonnes de base Xxxxx.Models.XxxxxContext crées au cours de différents essais ou
tutoriels)&lt;/li&gt;
&lt;li&gt;créer une nouvelle base &amp;quot;Elmah&amp;quot; (et la passer en Compatibility_Level
80)&lt;/li&gt;
&lt;li&gt;lancer le script (qui génère pas mal de message d'avertissement)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ouf ! C'est fait.&lt;/p&gt;
&lt;p&gt;Puis enregistrer la chaine de connexion :&lt;/p&gt;
&lt;pre&gt;
&amp;lt;add name=&amp;quot;elmah-sqlserver&amp;quot;
     connectionString=&amp;quot;Data Source=.\SQLEXPRESS;Integrated Security=SSPI;Initial Catalog=Elmah;&amp;quot;
     providerName=&amp;quot;System.Data.SqlClient&amp;quot; /&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Je peux alors relancer l'application, provoquer une erreur et aller que la
page elmah.axd et vérifier que &lt;code&gt;&amp;quot;This log is provided by the Microsoft SQL
Server Error Log.&amp;quot;&lt;/code&gt;. Bravo !&lt;/p&gt;
&lt;h3&gt;ELMAH + Sql Server Compact en manuel&lt;/h3&gt;
&lt;p&gt;Après désinstallation du package &amp;quot;ELMAH on MS SQL Server (requires manual
config)&amp;quot;, je me retrouve à nouveau avec un ELMAH tout simple qui stocke ses
erreurs en mémoire. Et au passage, le dossier App_Readme a disparu.
Magnifique !&lt;/p&gt;
&lt;p&gt;Je peux donc tenter une modification tout ce qu'il y a de plus manuelle du
web.config pour utiliser une base de données Sql Server Compact (celui qui est
déjà installé sur mon PC).&lt;/p&gt;
&lt;p&gt;Un :&lt;/p&gt;
&lt;pre&gt;
&amp;lt;connectionStrings&amp;gt;
  ...
  &amp;lt;add name=&amp;quot;elmah-sqlservercompact&amp;quot;
       connectionString=&amp;quot;Data Source=|DataDirectory|\Elmah.sdf&amp;quot; /&amp;gt;
&amp;lt;/connectionStrings&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Et deux :&lt;/p&gt;
&lt;pre&gt;
&amp;lt;elmah&amp;gt;
  &amp;lt;errorLog type=&amp;quot;Elmah.SqlServerCompactErrorLog, Elmah&amp;quot;
            connectionStringName=&amp;quot;elmah-sqlservercompact&amp;quot; /&amp;gt;
&amp;lt;/elmah&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Je relance l'application, tente d'accéder à l'URL index.html qui n'existe
pas puis direction elmah.axd et ça marche : &lt;code&gt;&amp;quot;This log is provided by
the SQL Server Compact Error Log&amp;quot;&lt;/code&gt;. Magnifique !&lt;/p&gt;
&lt;h3&gt;Conclusions&lt;/h3&gt;
&lt;p&gt;Déjà, le système des packages NuGet est bien foutu (au moins en ce qui
concerne ELMAH) :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ça installe ce qui est nécessaire et ça fait les modifications et
configurations qui vont bien où ça va bien.&lt;/li&gt;
&lt;li&gt;quand on désinstalle, ça sait remettre tout comme il faut, y compris les
modifications apportées au web.config :)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pour l'instant, je vais en rester à la configuration manuelle pour Sql
Server Compact. C'est bien suffisant pour les essais que je fais.&lt;/p&gt;
&lt;p&gt;Mais finalement, je pense avoir compris pourquoi le package &amp;quot;ELMAH on MS SQL
Server Compact&amp;quot; en automatique installe SqlServerCompact. Au moins, lorsque on
déploie sur le serveur de production, ça permet que ELMAH soit prêt à
fonctionner sur une base de données SQL Server Compact, même si celui-ci n'est
pas installé en production. Pas bête !&lt;/p&gt;</description>
    
    
    
          <comments>http://blog.pagesd.info/post/2011/12/23/configurer-elmah-pour-utiliser-sql-server#comment-form</comments>
      <wfw:comment>http://blog.pagesd.info/post/2011/12/23/configurer-elmah-pour-utiliser-sql-server#comment-form</wfw:comment>
      <wfw:commentRss>http://blog.pagesd.info/feed/atom/comments/648303</wfw:commentRss>
      </item>
    
  <item>
    <title>MVC Scaffolding, Eager loading et ViewModel</title>
    <link>http://blog.pagesd.info/post/2011/11/17/mvc-scaffolding-eager-loading-viewmodel</link>
    <guid isPermaLink="false">urn:md5:fd7bd328c7ddafd497c7b50ee82e0c42</guid>
    <pubDate>Thu, 17 Nov 2011 19:53:00 +0100</pubDate>
    <dc:creator>Michel</dc:creator>
        <category>EF</category><category>MVC</category>    
    <description>    &lt;p&gt;Pour tenter de mettre au point une méthode simple(iste) pour construire une
application avec ASP.NET MVC, j'essaie de développer une petite application
destinées à générer une brochure de voyages . Pour cela, je dois gérer une base
de données avec une table &amp;quot;Voyages&amp;quot;, une table &amp;quot;Tarifs&amp;quot; pour enregistrer les
chaque tarif du voyage et une table &amp;quot;Parties&amp;quot; qui sert à décrire les
différentes étapes du voyage.&lt;/p&gt;
&lt;h3&gt;Eager loading pour les actions Index&lt;/h3&gt;
&lt;p&gt;Pour l'instant, j'ai plus ou moins laissé &lt;a href=&quot;http://mvcscaffolding.codeplex.com/&quot; hreflang=&quot;us&quot;&gt;MVC Scaffolding&lt;/a&gt; écrire
mon code pour la classe Voyage associée à la table Voyages. Par défaut, il a
généré le code C# suivant pour l'action Index du contrôleur VoyagesController
afin retrouver tous les voyages à afficher :&lt;/p&gt;
&lt;pre&gt;
var voyages = voyageRepository
    .AllIncluding(voyage =&amp;gt; voyage.Parties, voyage =&amp;gt; voyage.Tarifs)
    .OrderBy(voyage =&amp;gt; voyage.Position)
    .ThenBy(voyage =&amp;gt; voyage.Title);
&lt;/pre&gt;
&lt;p&gt;Ce qui correspond à quelque chose dans ce genre si on ne passe pas par un
Repository :&lt;/p&gt;
&lt;pre&gt;
var voyages = context
    .Voyages
    .Include(voyage =&amp;gt; voyage.Parties)
    .Include(voyage =&amp;gt; voyage.Tarifs)
    .OrderBy(voyage =&amp;gt; voyage.Position)
    .ThenBy(voyage =&amp;gt; voyage.Title);
&lt;/pre&gt;
&lt;p&gt;Ces deux codes C# ont tous les deux pour effet de générer une (grosse)
requête SQL qui charge à la fois le contenu de la table Voyages et par des
jointures celui des tables Parties et Tarifs :&lt;/p&gt;
&lt;pre&gt;
SELECT 
[UnionAll1].[VoyageID] AS [C1], 
[UnionAll1].[VoyageID1] AS [C2], 
[UnionAll1].[Position1] AS [C3], 
[UnionAll1].[Title1] AS [C4], 
[UnionAll1].[VoyageType] AS [C5], 
[UnionAll1].[Notes] AS [C6], 
[UnionAll1].[C1] AS [C7], 
[UnionAll1].[PartieID] AS [C8], 
[UnionAll1].[VoyageID2] AS [C9], 
[UnionAll1].[Position2] AS [C10], 
[UnionAll1].[PartieType] AS [C11], 
[UnionAll1].[Content] AS [C12], 
[UnionAll1].[C2] AS [C13], 
[UnionAll1].[C3] AS [C14], 
[UnionAll1].[C4] AS [C15], 
[UnionAll1].[C5] AS [C16], 
[UnionAll1].[C6] AS [C17], 
[UnionAll1].[C7] AS [C18], 
[UnionAll1].[C8] AS [C19], 
[UnionAll1].[C9] AS [C20], 
[UnionAll1].[C10] AS [C21], 
[UnionAll1].[C11] AS [C22]
FROM  (SELECT
        CASE WHEN ([Extent2].[PartieID] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1],
        [Extent1].[Position] AS [Position], 
        [Extent1].[Title] AS [Title], 
        [Extent1].[VoyageID] AS [VoyageID], 
        [Extent1].[VoyageID] AS [VoyageID1], 
        [Extent1].[Position] AS [Position1], 
        [Extent1].[Title] AS [Title1], 
        [Extent1].[VoyageType] AS [VoyageType], 
        [Extent1].[Notes] AS [Notes], 
        [Extent2].[PartieID] AS [PartieID], 
        [Extent2].[VoyageID] AS [VoyageID2], 
        [Extent2].[Position] AS [Position2], 
        [Extent2].[PartieType] AS [PartieType], 
        [Extent2].[Content] AS [Content], 
        CAST(NULL AS int) AS [C2], 
        CAST(NULL AS int) AS [C3], 
        CAST(NULL AS nvarchar(1)) AS [C4], 
        CAST(NULL AS nvarchar(1)) AS [C5], 
        CAST(NULL AS real) AS [C6], 
        CAST(NULL AS real) AS [C7], 
        CAST(NULL AS real) AS [C8], 
        CAST(NULL AS real) AS [C9], 
        CAST(NULL AS real) AS [C10], 
        CAST(NULL AS nvarchar(1)) AS [C11] 
        FROM  [Voyages] AS [Extent1] 
        LEFT OUTER JOIN [Parties] AS [Extent2] ON [Extent1].[VoyageID] = [Extent2].[VoyageID]
UNION ALL
        SELECT
        2 AS [C1], 
        [Extent3].[Position] AS [Position], 
        [Extent3].[Title] AS [Title], 
        [Extent3].[VoyageID] AS [VoyageID], 
        [Extent3].[VoyageID] AS [VoyageID1], 
        [Extent3].[Position] AS [Position1], 
        [Extent3].[Title] AS [Title1], 
        [Extent3].[VoyageType] AS [VoyageType], 
        [Extent3].[Notes] AS [Notes], 
        CAST(NULL AS int) AS [C2], 
        CAST(NULL AS int) AS [C3], 
        CAST(NULL AS int) AS [C4], 
        CAST(NULL AS int) AS [C5], 
        CAST(NULL AS nvarchar(1)) AS [C6], 
        [Extent4].[TarifID] AS [TarifID], 
        [Extent4].[VoyageID] AS [VoyageID2], 
        [Extent4].[Title] AS [Title2], 
        [Extent4].[Year] AS [Year], 
        [Extent4].[Prix1] AS [Prix1], 
        [Extent4].[Prix2] AS [Prix2], 
        [Extent4].[Prix3] AS [Prix3], 
        [Extent4].[Prix4] AS [Prix4], 
        [Extent4].[Prix5] AS [Prix5], 
        [Extent4].[Notes] AS [Notes1]
        FROM  [Voyages] AS [Extent3]
        INNER JOIN [Tarifs] AS [Extent4] ON [Extent3].[VoyageID] = [Extent4].[VoyageID]) AS [UnionAll1]
ORDER BY [UnionAll1].[Position] ASC, [UnionAll1].[Title] ASC, [UnionAll1].[VoyageID1] ASC, [UnionAll1].[C1] ASC  
&lt;/pre&gt;
&lt;p&gt;Puis dans la vue Index.cshtml, on utilise le
&lt;code&gt;IEnumerable&amp;lt;Voyage&amp;gt;&lt;/code&gt; obtenu pour afficher une liste des
voyages, avec pour chacun d'entre eux le nombre de parties qui composent sa
description détaillée et le nombre de tarifs définis pour ce voyage.&lt;/p&gt;
&lt;pre&gt;
@foreach (var item in Model) {
  &amp;lt;tr&amp;gt;
    &amp;lt;td&amp;gt;
      @Html.ActionLink(&amp;quot;Edit&amp;quot;, &amp;quot;Edit&amp;quot;, new { id = item.VoyageID })
    &amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;
      @item.Position
    &amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;
      @Html.ActionLink(@item.Title, &amp;quot;Details&amp;quot;, new { id = item.VoyageID })
    &amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;
      @Html.DisplayTextFor(_ =&amp;gt; item.TypeVoyage).ToString()
    &amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;
      @(item.Parties == null ? &amp;quot;&amp;quot; : item.Parties.Count.ToString())
    &amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;
      @(item.Tarifs == null ? &amp;quot;&amp;quot; : item.Tarifs.Count.ToString())
    &amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;
      @Html.ActionLink(&amp;quot;Delete&amp;quot;, &amp;quot;Delete&amp;quot;, new { id = item.VoyageID })
    &amp;lt;/td&amp;gt;
  &amp;lt;/tr&amp;gt;
}
&lt;/pre&gt;
&lt;p&gt;Ce qui est intéressant, c'est que MVC Scaffolding fait automatiquement du
&amp;quot;Eager Loading&amp;quot; pour que les instructions
&lt;code&gt;item.Parties.Count.ToString()&lt;/code&gt; et
&lt;code&gt;item.Tarifs.Count.ToString()&lt;/code&gt; en boucle ne provoquent pas autant
d'accès à la base de données qu'il y a de voyage à afficher.&lt;/p&gt;
&lt;h3&gt;Lazy loading et SELECT N+1&lt;/h3&gt;
&lt;p&gt;En effet, si par défaut, MVC Scaffolding n'avait pas fait le lien avec les
parties et les tarifs et qu'il se soit contenté de générer le code C#
suivant :&lt;/p&gt;
&lt;pre&gt;
var voyages = voyageRepository
    .All
    .OrderBy(voyage =&amp;gt; voyage.Position)
    .ThenBy(voyage =&amp;gt; voyage.Title);
&lt;/pre&gt;
&lt;p&gt;Ou sans le Repository :&lt;/p&gt;
&lt;pre&gt;
var voyages = context
    .Voyages
    .OrderBy(voyage =&amp;gt; voyage.Position)
    .ThenBy(voyage =&amp;gt; voyage.Title);
&lt;/pre&gt;
&lt;p&gt;Alors la consultation de la page Index n'aurait pas généré 1 seule requête,
mais 71 !!!&lt;/p&gt;
&lt;p&gt;Soit 1 première requête toute simple pour charger la liste des 35
voyages :&lt;/p&gt;
&lt;pre&gt;
SELECT 
[Extent1].[VoyageID] AS [VoyageID], 
[Extent1].[Position] AS [Position], 
[Extent1].[Title] AS [Title], 
[Extent1].[VoyageType] AS [VoyageType], 
[Extent1].[Notes] AS [Notes]
FROM [Voyages] AS [Extent1]
ORDER BY [Extent1].[Position] ASC, [Extent1].[Title] ASC 
&lt;/pre&gt;
&lt;p&gt;Puis 35 requêtes pour charger successivement les parties de chacun des 35
voyages :&lt;/p&gt;
&lt;pre&gt;
SELECT 
[Extent1].[PartieID] AS [PartieID], 
[Extent1].[VoyageID] AS [VoyageID], 
[Extent1].[Position] AS [Position], 
[Extent1].[PartieType] AS [PartieType], 
[Extent1].[Content] AS [Content]
FROM [Parties] AS [Extent1]
WHERE [Extent1].[VoyageID] = @EntityKeyValue1  
&lt;/pre&gt;
&lt;p&gt;Et encore 35 requêtes pour charger successivement les tarifs de chacun des
35 voyages :&lt;/p&gt;
&lt;pre&gt;
SELECT 
[Extent1].[TarifID] AS [TarifID], 
[Extent1].[VoyageID] AS [VoyageID], 
[Extent1].[Title] AS [Title], 
[Extent1].[Year] AS [Year], 
[Extent1].[Prix1] AS [Prix1], 
[Extent1].[Prix2] AS [Prix2], 
[Extent1].[Prix3] AS [Prix3], 
[Extent1].[Prix4] AS [Prix4], 
[Extent1].[Prix5] AS [Prix5], 
[Extent1].[Notes] AS [Notes]
FROM [Tarifs] AS [Extent1]
WHERE [Extent1].[VoyageID] = @EntityKeyValue1   
&lt;/pre&gt;
&lt;p&gt;Par rapport à l'eager loading, ici on a fait du &amp;quot;lazy loading&amp;quot; : les
entités enfants ne sont chargées que lorsque on y accède. Des fois c'est bien.
Des fois c'est mal. Mais heureusement, MVC Scaffolding qui a pensé à tout est
est assez malin pour nous éviter l'écueil du &lt;a href=&quot;http://stackoverflow.com/questions/97197/what-is-the-n1-selects-problem&quot; hreflang=&quot;us&quot;&gt;SELECT N+1&lt;/a&gt; quand on génère l'action Index (&lt;a href=&quot;http://blog.stevensanderson.com/2011/01/28/mvcscaffolding-one-to-many-relationships/&quot; hreflang=&quot;us&quot;&gt;merci Scott&lt;/a&gt;).&lt;/p&gt;
&lt;h3&gt;Lazy loading et virtual&lt;/h3&gt;
&lt;p&gt;Pour être complet, c'est parce que les propriétés Parties et Tarifs de la
classe Voyage sont déclarées en tant que propriétés virtuelles, que Entity
Framework a fait du &amp;quot;lazy loading&amp;quot;.&lt;/p&gt;
&lt;p&gt;Ainsi, si au lieu d'utiliser &lt;code&gt;virtual&lt;/code&gt; pour les
déclarer :&lt;/p&gt;
&lt;pre&gt;
public virtual ICollection&amp;lt;Partie&amp;gt; Parties { get; set; }
public virtual ICollection&amp;lt;Tarif&amp;gt; Tarifs { get; set; }
&lt;/pre&gt;
&lt;p&gt;On s'était contenté de :&lt;/p&gt;
&lt;pre&gt;
public ICollection&amp;lt;Partie&amp;gt; Parties { get; set; }
public ICollection&amp;lt;Tarif&amp;gt; Tarifs { get; set; }
&lt;/pre&gt;
&lt;p&gt;Alors la consultation de la page Index n'aurait plus généré qu'une seule
requête pour charger uniquement les données de la table des voyages (pas de
eager loading puisque on n'a pas d'&lt;code&gt;Include&lt;/code&gt;) :&lt;/p&gt;
&lt;pre&gt;
SELECT 
[Extent1].[VoyageID] AS [VoyageID], 
[Extent1].[Position] AS [Position], 
[Extent1].[Title] AS [Title], 
[Extent1].[VoyageType] AS [VoyageType], 
[Extent1].[Notes] AS [Notes]
FROM [Voyages] AS [Extent1]
ORDER BY [Extent1].[Position] ASC, [Extent1].[Title] ASC 
&lt;/pre&gt;
&lt;p&gt;Et puis c'est tout ! Comme on n'a pas de &lt;code&gt;virtual&lt;/code&gt;, Entity
Framework ne fait pas non plus de lazy loading et ne cherche donc pas à charger
les parties et les tarifs.&lt;/p&gt;
&lt;p&gt;C'est pour cela que les colonnes nombre de parties et nombre de tarifs
restent vides puisque les instructions &lt;code&gt;item.Parties == null ?
&amp;quot;&amp;quot; : item.Xxxxxx.Count.ToString()&lt;/code&gt; sont là pour gérer le fait que
les collections voyage.Parties et voyage.Tarifs sont nulles.&lt;/p&gt;
&lt;p&gt;Par contre, même sans le &lt;code&gt;virtual&lt;/code&gt;, si on demande à faire de
l'eager loading à grand coups de &lt;code&gt;Include&lt;/code&gt;, on retombe bien sur la
grosse requête qui accède par jointure aux tables Voyages, Parties et
Tarifs.&lt;/p&gt;
&lt;h3&gt;Action Index et ViewModel&lt;/h3&gt;
&lt;p&gt;A titre personnel, je trouve malgré tout que la requête SQL générée par
Entity Framework est un peu lourde pour ce qu'on en fait. Alors que le but est
simplement d'afficher le nombre de parties et de tarifs d'un voyage, on se
retrouve quand même à charger toutes les parties et tous les tarifs de tous les
voyages, ce qui revient donc à charger l'intégralité des tables Parties et
Tarifs.&lt;/p&gt;
&lt;p&gt;&lt;ins&gt;Note&lt;/ins&gt; : Si on avait une pagination par paquet de 20 voyages,
ça serait un peu mieux puisque cela reviendrait &amp;quot;seulement&amp;quot; à charger toutes
les parties et tous les tarifs correspondant à 20 voyages uniquement.&lt;/p&gt;
&lt;p&gt;Dans l'idéal, il faudrait donc récupérer les informations de base de chaque
voyage sans ses propriétés voyage.Parties et voyage.Tarifs, plus le nombre de
ses parties et le nombre de ses tarifs. Ce qui pourrait être représenté par la
classe ViewModel suivante :&lt;/p&gt;
&lt;pre&gt;
public class VoyageViewModel
{
    public int VoyageID { get; set; }
    public int Position { get; set; }
    public string Title { get; set; }
    public int VoyageType { get; set; }
    public int TarifsCount { get; set; }
    public int PartiesCount { get; set; }
}
&lt;/pre&gt;
&lt;p&gt;Le fait d'utiliser un ViewModel est d'autant plus intéressant qu'une liste
n'est pas là pour afficher la totalité des propriétés d'une entité, mais
seulement quelques informations essentielles pour remplir son rôle, à savoir
permettre de retrouver d'un coup d'oeil les informations qui comptent et
éventuellement accéder au détail de la fiche pour consulter l'ensemble de ses
données.&lt;/p&gt;
&lt;p&gt;Par exemple, si j'ai une table Contacts avec civilité, nom, prénom,
téléphone, portable, fax, email, adresse, code postal, ville, pays, format pour
les emails, date de naissance, etc... je ne veux pas d'une liste qui fassent 50
colonnes pour afficher tout ça. Je préfère sacrifier quelques détails et me
contenter d'une liste édulcorée mais lisible.&lt;/p&gt;
&lt;p&gt;Pour résumer, je pense que dans le cas d'une liste (au moins), il est
préférable d'employer un objet ViewModel spécifique pour collecter les données
importantes puis de transmettre cet objet à la vue pour qu'elle présente ces
informations à l'utilisateur.&lt;/p&gt;
&lt;p&gt;Cela implique donc de créer une classe ViewModel &amp;quot;VoyagesIndex&amp;quot; (nommée
d'après le nom du contrôleur (VoyagesController) et de l'action (Index) qui va
l'utiliser) :&lt;/p&gt;
&lt;pre&gt;
public class VoyagesIndex
{
    public int VoyageID { get; set; }
    public int Position { get; set; }
    public string Title { get; set; }
    public int VoyageType { get; set; }
    public int TarifsCount { get; set; }
    public int PartiesCount { get; set; }

    public VoyageType TypeVoyage
    {
        get { return (VoyageType)VoyageType; }
        set { VoyageType = (int)value; }
    }
}
&lt;/pre&gt;
&lt;p&gt;&lt;ins&gt;Note&lt;/ins&gt; : &lt;code&gt;public VoyageType TypeVoyage&lt;/code&gt; est une
bidouille qui n'a rien à voir avec le sujet et que j'essairai d'expliquer plus
tard si je persiste à passer par elle.&lt;/p&gt;
&lt;p&gt;Il faut ensuite modifier le code de l'action Index pour qu'elle récupère une
collection de VoyagesIndex et plus une collection d'objets Voyage :&lt;/p&gt;
&lt;pre&gt;
var voyages = voyageRepository
    .All
    .OrderBy(voyage =&amp;gt; voyage.Position)
    .ThenBy(voyage =&amp;gt; voyage.Title)
    .Select(voyage =&amp;gt; new VoyagesIndex
    {
        VoyageID = voyage.VoyageID
        Position = voyage.Position,
        Title = voyage.Title,
        VoyageType = voyage.VoyageType,
        TarifsCount = voyage.Tarifs.Count(),
        PartiesCount = voyage.Parties.Count()
    }).ToList();
&lt;/pre&gt;
&lt;p&gt;Et pour finir modifier la vue Index.cshtml pour qu'elle utilise désormais un
&lt;code&gt;IEnumerable&amp;lt;VoyagesIndex&amp;gt;&lt;/code&gt; pour afficher son
contenu :&lt;/p&gt;
&lt;pre&gt;
@foreach (var item in Model) {
  &amp;lt;tr&amp;gt;
    &amp;lt;td&amp;gt;
      @Html.ActionLink(&amp;quot;Edit&amp;quot;, &amp;quot;Edit&amp;quot;, new { id = item.VoyageID })
    &amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;
      @item.Position
    &amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;
      @Html.ActionLink(@item.Title, &amp;quot;Details&amp;quot;, new { id = item.VoyageID })
    &amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;
      @item.TypeVoyage.ToString()
    &amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;
      @item.PartiesCount
    &amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;
      @item.TarifsCount
    &amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;
      @Html.ActionLink(&amp;quot;Delete&amp;quot;, &amp;quot;Delete&amp;quot;, new { id = item.VoyageID })
    &amp;lt;/td&amp;gt;
  &amp;lt;/tr&amp;gt;
}
&lt;/pre&gt;
&lt;p&gt;Grâce à quoi la requête SQL générée quand on consulte la page Index est un
peu plus simple à lire, et normalement plus efficace puisqu'elle ne cherche pas
à charger les tables Parties et Tarifs.&lt;/p&gt;
&lt;pre&gt;
SELECT 
[Project2].[VoyageID] AS [VoyageID], 
[Project2].[Position] AS [Position], 
[Project2].[Title] AS [Title], 
[Project2].[VoyageType] AS [VoyageType], 
[Project2].[C1] AS [C1], 
[Project2].[C2] AS [C2]
FROM ( SELECT 
        [Project1].[VoyageID] AS [VoyageID], 
        [Project1].[Position] AS [Position], 
        [Project1].[Title] AS [Title], 
        [Project1].[VoyageType] AS [VoyageType], 
        [Project1].[C1] AS [C1], 
        [SSQTAB1].[A1] AS [C2] 
        FROM ( SELECT 
                [Extent1].[VoyageID] AS [VoyageID], 
                [Extent1].[Position] AS [Position], 
                [Extent1].[Title] AS [Title], 
                [Extent1].[VoyageType] AS [VoyageType], 
                [SSQTAB1].[A1] AS [C1] 
                FROM [Voyages] AS [Extent1] 
                 OUTER APPLY 
                (SELECT 
                        COUNT(1) AS [A1] 
                        FROM [Tarifs] AS [Extent2] 
                        WHERE [Extent1].[VoyageID] = [Extent2].[VoyageID]) AS [SSQTAB1]
        )  AS [Project1]
         OUTER APPLY
        (SELECT
                COUNT(1) AS [A1]
                FROM [Parties] AS [Extent3]
                WHERE [Project1].[VoyageID] = [Extent3].[VoyageID]) AS [SSQTAB1]
)  AS [Project2]
ORDER BY [Project2].[Position] ASC, [Project2].[Title] ASC   
&lt;/pre&gt;</description>
    
    
    
      </item>
    
  <item>
    <title>Utilisation d’expressions lambda en 10 étapes</title>
    <link>http://blog.pagesd.info/post/2011/10/18/utilisation-expressions-lambda-en-10-etapes</link>
    <guid isPermaLink="false">urn:md5:8c4e625edaf49a22e67c88b4422659e8</guid>
    <pubDate>Tue, 18 Oct 2011 18:59:00 +0200</pubDate>
    <dc:creator>Michel</dc:creator>
        <category>.net</category><category>c-sharp</category><category>Tutoriel</category>    
    <description>    &lt;h3&gt;Point de départ : 3 fonctions avec du code répétitif ( !DRY)&lt;/h3&gt;
&lt;h4&gt;PrevisionFacturationSrv.cs&lt;/h4&gt;
&lt;pre&gt;
PrevSrv.UpdateEndOfContract(PrevFinContrat);
PrevSrv.UpdateEndOfMonth(PrevFinMois);
PrevSrv.UpdatePlacement(PrevPlacement, PonderationPlacement);
&lt;/pre&gt;
&lt;h4&gt;PrevisionSrv.cs&lt;/h4&gt;
&lt;pre&gt;
/// &amp;lt;summary&amp;gt;
/// Màj de la prévision de facturation fin de contrat pour un ensemble de Siren / Société
/// &amp;lt;/summary&amp;gt;
/// &amp;lt;param name=&amp;quot;list&amp;quot;&amp;gt;Liste des couples Siren / Société et des montants à traiter&amp;lt;/param&amp;gt;
public void UpdateEndOfContract(IList&amp;lt;PrevisionFacturation&amp;gt; list)
{
  foreach (var pf in list)
  {
    var Prevision = this.GetBySirenAndSociety(pf.Siren, pf.Societe);

    Prevision.FinContrat = pf.Montant &amp;lt; 0 ? 0 : Math.Round(pf.Montant, 2);

    this.CreateOrUpdate(Prevision);
  }
}

/// &amp;lt;summary&amp;gt;
/// Màj de la prévision de facturation fin de mois pour un ensemble de Siren / Société
/// &amp;lt;/summary&amp;gt;
/// &amp;lt;param name=&amp;quot;list&amp;quot;&amp;gt;Liste des couples Siren / Société et des montants à traiter&amp;lt;/param&amp;gt;
public void UpdateEndOfMonth(IList&amp;lt;PrevisionFacturation&amp;gt; list)
{
  foreach (var pf in list)
  {
    var Prevision = this.GetBySirenAndSociety(pf.Siren, pf.Societe);

    Prevision.FinMois = pf.Montant &amp;lt; 0 ? 0 : Math.Round(pf.Montant, 2);
    Prevision.NbContrats = pf.Nombre;

    this.CreateOrUpdate(Prevision);
  }
}

/// &amp;lt;summary&amp;gt;
/// Màj de la prévision de facturation placement pour un ensemble de Siren / Société
/// &amp;lt;/summary&amp;gt;
/// &amp;lt;param name=&amp;quot;list&amp;quot;&amp;gt;Liste des couples Siren / Société et des montants à traiter&amp;lt;/param&amp;gt;
/// &amp;lt;param name=&amp;quot;ponderation&amp;quot;&amp;gt;Taux de pondération pour la prise en compte du placement&amp;lt;/param&amp;gt;
public void UpdatePlacement(IList&amp;lt;PrevisionFacturation&amp;gt; list, int ponderation)
{
  foreach (var pf in list)
  {
    var Prevision = this.GetBySirenAndSociety(pf.Siren, pf.Societe);

    Prevision.Placement = pf.Montant &amp;lt; 0 ? 0 : Math.Round(pf.Montant * ponderation / 100, 2);

    this.CreateOrUpdate(Prevision);
  }
}
&lt;/pre&gt;
&lt;h3&gt;Source d'inspiration (à lire !)&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;http://fguillot.developpez.com/cours/dotnet/introduction-delegates-csharp/&quot; hreflang=&quot;fr&quot;&gt;Introduction au délégués en C#&lt;/a&gt;, un article en français rédigé
par Fabien Guillot pour expliquer aux débutants comment fonctionnent les
délégués en C#, quelle est leur utilité, et quelle a été leur évolution avec
les différentes versions du Framework .NET.&lt;/p&gt;
&lt;h3&gt;Les 10 étapes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1° étape : Mise en commun du code (ancien style)&lt;/li&gt;
&lt;li&gt;2° étape : Isolation des traitements spécifiques dans des fonctions
séparées&lt;/li&gt;
&lt;li&gt;3° étape : Mise en place des délégués (enfin !)&lt;/li&gt;
&lt;li&gt;4° étape : Simplification des délégués grâce à l’inférence de
type&lt;/li&gt;
&lt;li&gt;5° étape : Arrivée des méthodes anonymes (avec .NET 2)&lt;/li&gt;
&lt;li&gt;6° étape : Simplification des délégués anonymes grâce à l’inférence de
type&lt;/li&gt;
&lt;li&gt;7° étape : Retour sur le code : « ref » ne sert à rien
(merci Nicolas)&lt;/li&gt;
&lt;li&gt;8° étape : Arrivée des expressions lambdas (avec .NET 3.5)&lt;/li&gt;
&lt;li&gt;9° étape : Simplification des expressions lambdas&lt;/li&gt;
&lt;li&gt;10° étape : Utilisation de paramètres optionnels&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Consulter éventuellement la version PDF pour voir en détail comment se sont
déroulées ces étapes : &lt;a href=&quot;http://bit.ly/qsAcx1&quot; hreflang=&quot;fr&quot;&gt;Utilisation_Expressions_Lambda_en_10_etapes.pdf&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Le résultat&lt;/h3&gt;
&lt;h4&gt;PrevisionFacturationSrv.cs&lt;/h4&gt;
&lt;pre&gt;
PrevSrv.UpdatePrevision(PrevFinContrat, (p, m, n) =&amp;gt; { p.FinContrat = m; });
PrevSrv.UpdatePrevision(PrevFinMois, (p, m, n) =&amp;gt; { p.FinMois = m; p.NbContrats = n; });
PrevSrv.UpdatePrevision(PrevPlacement, (p, m, n) =&amp;gt; { p.Placement = m; }, PonderationPlacement);
&lt;/pre&gt;
&lt;h4&gt;PrevisionSrv.cs&lt;/h4&gt;
&lt;pre&gt;
/// &amp;lt;summary&amp;gt;
/// Mise à jour de la prévision de facturation pour un ensemble de Siren / Société
/// &amp;lt;/summary&amp;gt;
/// &amp;lt;param name=&amp;quot;list&amp;quot;&amp;gt;Liste des couples Siren / Société et des montants à traiter&amp;lt;/param&amp;gt;
/// &amp;lt;param name=&amp;quot;assign&amp;quot;&amp;gt;Méthode pour réaliser le traitement spécifique&amp;lt;/param&amp;gt;
/// &amp;lt;param name=&amp;quot;ponderation&amp;quot;&amp;gt;Taux de pondération pour le type de prévision&amp;lt;/param&amp;gt;
public void UpdatePrevision(IList&amp;lt;PrevisionFacturation&amp;gt; list, UpdateAssign assign, int ponderation = 100)
{
  foreach (var pf in list)
  {
    pf.Montant = pf.Montant &amp;lt; 0 ? 0 : Math.Round(pf.Montant * ponderation / 100, 2);

    var Prevision = this.GetBySirenAndSociety(pf.Siren, pf.Societe);

    assign(Prevision, pf.Montant, pf.Nombre);

    this.CreateOrUpdate(Prevision);
  }
}

/// &amp;lt;summary&amp;gt;
/// Signature des fonctions d'affectation spécifiques
/// &amp;lt;/summary&amp;gt;
/// &amp;lt;param name=&amp;quot;prevision&amp;quot;&amp;gt;Objet Prevision à mettre à jour&amp;lt;/param&amp;gt;
/// &amp;lt;param name=&amp;quot;montant&amp;quot;&amp;gt;Montant de la prévision&amp;lt;/param&amp;gt;
/// &amp;lt;param name=&amp;quot;nombre&amp;quot;&amp;gt;Nombre de contrats traités (ssi fin de mois)&amp;lt;/param&amp;gt;
public delegate void UpdateAssign(Prevision prevision, decimal montant, int nombre);
&lt;/pre&gt;</description>
    
    
    
      </item>
    
  <item>
    <title>Personnalisation de la CSS pour ASP.NET MVC</title>
    <link>http://blog.pagesd.info/post/2011/10/17/personnalisation-css-asp-net-mvc</link>
    <guid isPermaLink="false">urn:md5:79bc0c9dbff9706214de80eca3b8064b</guid>
    <pubDate>Mon, 17 Oct 2011 20:25:00 +0200</pubDate>
    <dc:creator>Michel</dc:creator>
        <category>css</category><category>html</category><category>MVC</category>    
    <description>    &lt;p&gt;Ce billet est avant tout un pense-bête personnel pour noter les quelques
modifications que j'apporte assez régulièrement à la feuille de style fournie
par défaut quand on génère un nouveau projet ASP.NET MVC3 dans Visual
Studio.&lt;/p&gt;
&lt;p&gt;En général, lorsque je débute un nouveau projet, j'ai tendance à conserver
la feuille de style telle que Visual Studio l'a créée. Puis quand je bloque sur
des fonctionnalités ou des problèmes techniques, je m'accorde un petit moment
de détente en adaptant « Site.css » pour essayer d'embellir mon
projet avant de repartir de plus belle.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://blog.pagesd.info/public/illustrations/mysite01.png&quot; alt=&quot;mysite01.png&quot; style=&quot;display:block; margin:0 auto;&quot; title=&quot;CSS d'origine&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Normalize&lt;/h3&gt;
&lt;p&gt;Avant toute chose, et pour partir du bon pied, je démarre par un petit
&lt;a href=&quot;http://meyerweb.com/eric/tools/css/reset/&quot; hreflang=&quot;us&quot;&gt;reset.css&lt;/a&gt;, ou ces derniers temps un &lt;a href=&quot;http://necolas.github.com/normalize.css/&quot; hreflang=&quot;us&quot;&gt;normalize.css&lt;/a&gt; dans
la vue _Layout.cshtml :&lt;/p&gt;
&lt;pre&gt;
&amp;lt;head&amp;gt;
  ...
  &amp;lt;link href=&amp;quot;@Url.Content(&amp;quot;~/Content/normalize.css&amp;quot;)&amp;quot; rel=&amp;quot;stylesheet&amp;quot; type=&amp;quot;text/css&amp;quot; /&amp;gt;
  &amp;lt;link href=&amp;quot;@Url.Content(&amp;quot;~/Content/Site.css&amp;quot;)&amp;quot; rel=&amp;quot;stylesheet&amp;quot; type=&amp;quot;text/css&amp;quot; /&amp;gt;
  ...
&amp;lt;/head&amp;gt;
&lt;/pre&gt;
&lt;h3&gt;Undo&lt;/h3&gt;
&lt;p&gt;Comme j'aime très moyennement les coins arrondis, je commence par m'en
débarrasser. Ca m'amuse toujours de commencer de façon radicale en lançant un
rechercher / remplacer de &lt;code&gt;-radius&lt;/code&gt; par &lt;code&gt;-no-radius&lt;/code&gt;.
Cela me permet de venir à bout de &lt;code&gt;border-radius&lt;/code&gt;,
&lt;code&gt;-moz-border-radius&lt;/code&gt; et autre &lt;code&gt;-webkit-border-radius&lt;/code&gt;.
Mais au final, je fais ça de façon plus civilisée en redéfinissant tous les
styles « xxxxx-border-radius » à 0.&lt;/p&gt;
&lt;pre&gt;
/* Suppression des coinzarrondis */
#main,
footer, 
#footer,
ul#menu li a
{
  border-radius: 0;
  -webkit-border-radius: 0;
  -moz-border-radius: 0;
}
&lt;/pre&gt;
&lt;p&gt;Il y a aussi l'effet d'ombre 3D sur le titre « My MVC
Application » que je trouve un peu lourd. Là encore, un rechercher /
remplacer à l'arrache de &lt;code&gt;-shadow&lt;/code&gt; par &lt;code&gt;-no-shadow&lt;/code&gt; ne
fait pas de mal. Ou plus proprement je redéfini le style
« text-shadow » de « header h1 ».&lt;/p&gt;
&lt;pre&gt;
/* Suppression de l'effet 3D sur le titre */
header h1,
#header h1
{
  text-shadow: 0 0 0 transparent;
}
&lt;/pre&gt;
&lt;h3&gt;Font&lt;/h3&gt;
&lt;p&gt;Une fois défoulé, j'apporte encore quelques modifications encore plus
personnelles pour obtenir un résultat un peu plus à mon goût.&lt;/p&gt;
&lt;p&gt;Comme je suis toujours dans ma période Century Gothic is Beautiful, c'est
elle qui gagne par rapport à la police « Trebuchet MS » utilisée par
défaut.&lt;/p&gt;
&lt;pre&gt;
/* Utilisation de ma police de prédilection */
body
{
  font-family: &amp;quot;Century Gothic&amp;quot;, &amp;quot;Trebuchet MS&amp;quot;, Verdana, Helvetica, Sans-Serif;
}
&lt;/pre&gt;
&lt;h3&gt;Color&lt;/h3&gt;
&lt;p&gt;Puis, même si j'aime beaucoup le bleu assez soutenu utilisé comme couleur de
fond, je le change pour une couleur plus claire et neutre.&lt;/p&gt;
&lt;p&gt;Ensuite, je change les couleurs de titres au gré de l'humeur du jour ou des
« Oh, c'est quoi cette joli couleur » trouvés sur d'autres sites. Ou
plus simplement, c'est le titre général en chocolat, le titre de page en gris
clair et les sous-titre en vert.&lt;/p&gt;
&lt;p&gt;Après, j'ai encore un problème avec le menu qui est bleuté par défaut parce
que je n'ai rien trouvé qui me plaise vraiment. Pour l'instant, je me contente
de le faire ressortir en le passant en blanc sur fond vert avec un effet
chocolat quand on passe la souris dessus.&lt;/p&gt;
&lt;pre&gt;
/* Changement du jeu de couleurs */
body { background-color: #e0e0e0; } /* Un fond de page gris clair */
header h1, #header h1, h1 { color: #d2691e; } /* chocolat */
h2 { color: #888888; } /* gris */
h3 { color: #39b449; } /* vert */
ul#menu li a { color: #fff; background-color: #39b449; } /* Menu blanc/vert */
ul#menu li a:hover { background-color: #d2691e; } /* Et survolé en chocolat */
ul#menu { border-bottom: 0; } /* Tant qu'à faire */
&lt;/pre&gt;
&lt;h3&gt;Table&lt;/h3&gt;
&lt;p&gt;Une fois zen, je pars à l'assaut des tables. Déjà c'est du n'importe quoi.
Pour éviter de m'emporter, je commence par un &lt;code&gt;table { width : 100%
}&lt;/code&gt; pour qu'elles s'étalent par défaut sur toute la largeur de la
page.&lt;/p&gt;
&lt;pre&gt;
/* Par défaut les tables occupent toute la largeur de la page */
table
{
  width: 100%;
}
&lt;/pre&gt;
&lt;p&gt;Puis j'aère les cellules parce que ça sert à rien de se payer des 21 pouces
si c'est pour rester tout riquiqui et tassé.&lt;/p&gt;
&lt;pre&gt;
/* Aération des cellules */
thead th,
tbody td,
tfoot th
{
  border: 1px dotted #aaa;
  padding: 0.5em;
  text-align: left;
  vertical-align: top;
}
&lt;/pre&gt;
&lt;p&gt;En parlant de &lt;code&gt;thead&lt;/code&gt; et autres, je me plonge dans toutes les
vues d'ores et déjà générées par défaut pour rajouter les balises
&lt;code&gt;&amp;lt;thead&amp;gt;&lt;/code&gt; et &lt;code&gt;&amp;lt;tbody&amp;gt;&lt;/code&gt; que Visual Studio
s'acharne à oublier. Y m'énerve ! C'est pourtant pas compliqué de
comprendre que :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;C'est quand même beaucoup plus simple à styler&lt;/li&gt;
&lt;li&gt;C'est bien plus pratique pour utiliser la plupart des plugins jQuery&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Après ça, je peux facilement colorer les en-têtes et pieds de tables en
reprenant la couleur du menu d'onglets :&lt;/p&gt;
&lt;pre&gt;
/* Redéfini les couleurs de l'en-tête et du pied des tables */
thead tr th,
tfoot tr th
{
  background-color: #3399ff;
  color: #fff;
}
&lt;/pre&gt;
&lt;p&gt;Puis là, comme j'en ai rien à faire des vieux navigateurs, j'alterne la
couleur de fond des lignes avec du CSS3 et un fond moins blanc que blanc.&lt;/p&gt;
&lt;pre&gt;
/* Alterne la couleur de fond des lignes d'une table */
tbody tr:nth-child(odd)
{
  background-color: #f0f0f0;
}
&lt;/pre&gt;
&lt;p&gt;Pour continuer à animer et égayer un peu les tableaux, je fais mon petit
effet au passage de la souris en changeant la couleur de fond de la ligne pour
que ça pète un peu plus. Là aussi, la couleur dépend des jours et de
l'inspiration. Et aujourd'hui c'est jaune clair parce qu'il a l'avantage
d'avoir un petit effet orangé sur mon portable.&lt;/p&gt;
&lt;pre&gt;
/* Ajoute un effet lors du survol d'une ligne */
tbody tr:hover
{
  background-color: #ffff80;
}
&lt;/pre&gt;
&lt;h3&gt;Form&lt;/h3&gt;
&lt;p&gt;Là aussi, il y aurait pas mal à dire du choix du balisage. Le problème,
c'est que c'est tout un ensemble entre les helpers et le système de validation
alors c'est un peu compliqué et que je résiste encore pour conserver tel quel
le html généré par Visual Studio. Ce qui fait que je n'arrive pas à me fixer
sur quelque chose de « définitif ». Enfin bref, y'a rien qui me plait pour
l'instant. A suivre donc…&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://blog.pagesd.info/public/illustrations/mysite02.png&quot; alt=&quot;mysite02.png&quot; style=&quot;display:block; margin:0 auto;&quot; title=&quot;Ma CSS personnalisée&quot; /&gt;&lt;/p&gt;</description>
    
    
    
      </item>
    
  <item>
    <title>Générer des URLs en minuscules avec ASP.NET MVC</title>
    <link>http://blog.pagesd.info/post/2011/10/06/generer-url-minuscule-asp-net-mvc</link>
    <guid isPermaLink="false">urn:md5:23267b8b7cd9abc5261220ddc22d658a</guid>
    <pubDate>Thu, 06 Oct 2011 20:47:00 +0200</pubDate>
    <dc:creator>Michel</dc:creator>
        <category>MVC</category><category>NuGet</category><category>Référencement</category>    
    <description>    &lt;p&gt;Un des trucs bien de ASP.NET MVC c'est que son système de routes sert aussi
pour générer automatiquement les URLs correctes pour les actions des
contrôleurs à partir de méthodes telles que Html.ActionLink(), Url.Action()… Un
des trucs crispant de ASP.NET MVC, c'est qu'il génère ces URLs en conservant
les noms des contrôleurs et des actions tels quels.&lt;/p&gt;
&lt;p&gt;Ainsi, si on a une action &amp;quot;Update&amp;quot; dans un contrôleur &amp;quot;ContactsController&amp;quot;,
cela génèrera l'URL &amp;quot;/Contacts/Update&amp;quot; alors que cela aurait été tellement plus
joli de générer une URL &amp;quot;/contacts/update&amp;quot; !&lt;/p&gt;
&lt;p&gt;Ce qui laisse 2 solutions :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Attendre le jour où ASP.NET MVC gèrera ça de lui-même&lt;/li&gt;
&lt;li&gt;Se dire que quelques majuscules par ci par là c'est pas si moche que
ça&lt;/li&gt;
&lt;li&gt;N'utiliser que des minuscules pour les noms de contrôleurs et
d'actions&lt;/li&gt;
&lt;li&gt;Améliorer le système de routes pour minusculiser automatiquement les noms
des contrôleurs et des actions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Du temps de MVC 1, j'avais déjà fait ça pour certains projets, en reprenant
le code proposé par Graham O'Neale dans son billet &lt;a href=&quot;http://goneale.com/2008/12/19/lowercase-route-urls-in-aspnet-mvc/&quot; hreflang=&quot;us&quot;&gt;Lowercase Route URL's in ASP.NET MVC&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Mais je trouve que c'est typiquement le genre de petits trucs sur lequel il
ne faut pas perdre de temps et qu'il ne faut pas chercher à gérer soi-même. Et
dans ce cas là, l'utilisation d'un package NuGet tout prêt est la solution
idéale.&lt;/p&gt;
&lt;p&gt;Ce qui tombe bien, puisque Lee Dumond a justement créé un package &lt;a href=&quot;http://nuget.org/List/Packages/LowercaseRoutesMVC&quot; hreflang=&quot;us&quot;&gt;LowercaseRouteMVC&lt;/a&gt; exprès pour ça. Son utilisation est très simple
puisqu'il suffit de référencer sa DLL puis de légèrement modifier la façon de
déclarer les routes dans le Globas.asax.&lt;/p&gt;
&lt;h3&gt;Installer&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Faire un clic droit sur la branche &amp;quot;Référence&amp;quot; du projet et sélectionner le
sous-menu &amp;quot;Manage NuGet Packages…&amp;quot;.&lt;/li&gt;
&lt;li&gt;Rechercher &amp;quot;lowercase&amp;quot;&lt;/li&gt;
&lt;li&gt;Demander à installer le package &amp;quot;LowercaseRouteMVC&amp;quot;&lt;/li&gt;
&lt;li&gt;Lire et accepter la licence&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Modifier&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Ouvrir le fichier &amp;quot;Global.asax&amp;quot;&lt;/li&gt;
&lt;li&gt;Ajouter un &lt;code&gt;using LowercaseRouteMVC;&lt;/code&gt; au bon endroit&lt;/li&gt;
&lt;li&gt;Modifier la procédure &amp;quot;RegisterRoutes&amp;quot; pour remplacer
&lt;code&gt;route.MapRoute&lt;/code&gt; par &lt;code&gt;route.MapRouteLowercase&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;
using LowercaseRoutesMVC;
  ...
public static void RegisterRoutes(RouteCollection routes)
{
  routes.IgnoreRoute(&amp;quot;{resource}.axd/{*pathInfo}&amp;quot;);
  
  routes.MapRouteLowercase(
    &amp;quot;Default&amp;quot;, // Route name
    &amp;quot;{controller}/{action}/{id}&amp;quot;, // URL with parameters
    new { controller = &amp;quot;Home&amp;quot;, action = &amp;quot;Index&amp;quot;, id = UrlParameter.Optional } // Parameter defaults
  );
&lt;/pre&gt;
&lt;h3&gt;Terminé&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Lancer l'exécution par F5&lt;/li&gt;
&lt;li&gt;Les URLs générées par l'application ne contiennent plus que des noms de
contrôleurs et d'actions en minuscules :)&lt;/li&gt;
&lt;/ul&gt;</description>
    
    
    
      </item>
    
  <item>
    <title>Configurer ELMAH pour envoyer les erreurs par email</title>
    <link>http://blog.pagesd.info/post/2011/09/29/configurer-elmah-envoi-email-erreur</link>
    <guid isPermaLink="false">urn:md5:189dcf7a3e5fdbff5517dd0686ec80bd</guid>
    <pubDate>Thu, 29 Sep 2011 20:57:00 +0200</pubDate>
    <dc:creator>Michel</dc:creator>
        <category>Elmah</category>    
    <description>    &lt;p&gt;Après avoir vu &lt;a href=&quot;http://blog.pagesd.info/post/2011/09/28/installer-elmah-avec-nuget&quot; hreflang=&quot;fr&quot;&gt;comment installer ELMAH avec NuGet&lt;/a&gt;, j'ai fait quelques tests avec pour
voir ce que ça pouvait donner avec différents types d'erreurs.&lt;/p&gt;
&lt;p&gt;Et quand je repense au peu d'effort que ça m'a demandé pour l'installer, le
résultat est quand même assez bluffant. Au moins ça donne envie d'aller plus
loin et d'étudier un peu mieux comment utiliser &amp;quot;réellement&amp;quot; ELMAH dans une
application.&lt;/p&gt;
&lt;p&gt;Un premier truc intéressant, c'est de s'occuper de paramétrer ELMAH afin de
recevoir un email quand qu'il détecte un problème. De cette façon, je serai
averti quasiment instantanément chaque fois que mon application provoquera une
exception. Ca fait un peu peur quand même...&lt;/p&gt;
&lt;p&gt;Concrètement, ELMAH est plutôt bien fait et il n'y a pas besoin de grand
chose pour paramétrer l'envoi d'email. Mais ce qui est encore plus génial,
c'est que l'essentiel du travail de paramétrage a déjà été fait lors de
l'installation via NuGet. Pour mémoire, il avait ajouté les 3 lignes suivantes
aux bons endroits dans le Web.config :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;section name=&amp;quot;errorMail&amp;quot; requirePermission=&amp;quot;false&amp;quot;
type=&amp;quot;Elmah.ErrorMailSectionHandler, Elmah&amp;quot; /&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;add name=&amp;quot;ErrorMail&amp;quot; type=&amp;quot;Elmah.ErrorMailModule, Elmah&amp;quot;
/&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;add name=&amp;quot;ErrorMail&amp;quot; type=&amp;quot;Elmah.ErrorMailModule, Elmah&amp;quot;
preCondition=&amp;quot;managedHandler&amp;quot; /&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pour ma part, il ne me reste plus qu'à ajouter une section
&lt;code&gt;&amp;lt;elmah&amp;gt;&lt;/code&gt; avec des informations personnelles :&lt;/p&gt;
&lt;pre&gt;
&amp;lt;configuration&amp;gt;
  ...
  &amp;lt;elmah&amp;gt;
    &amp;lt;errorMail
       from=&amp;quot;go@gmail.com&amp;quot;
       to=&amp;quot;michel@gmail.com&amp;quot;
       subject=&amp;quot;Application Exception&amp;quot;
       async=&amp;quot;true&amp;quot;
       smtpPort=&amp;quot;587&amp;quot;
       useSsl=&amp;quot;true&amp;quot;
       smtpServer=&amp;quot;smtp.gmail.com&amp;quot;
       userName=&amp;quot;go@mail.com&amp;quot;
       password=&amp;quot;mot_de_passe&amp;quot;&amp;gt;
    &amp;lt;/errorMail&amp;gt;
  &amp;lt;/elmah&amp;gt;
&lt;/pre&gt;
&lt;p&gt;&lt;ins&gt;Note&lt;/ins&gt; : étant donné que j'utilise GMail comme serveur SMTP,
il est nécessaire d'activer SSL (d'où le &lt;code&gt;useSsl=&amp;quot;true&amp;quot;&lt;/code&gt;) et de
préciser qu'il faut utiliser le port 587 (quoique ?, voir mon billet &lt;a href=&quot;http://blog.pagesd.info/post/2009/04/21/SystemNetMail-et-smtpgmailcom&quot; hreflang=&quot;fr&quot;&gt;System.Net.Mail et smtp.gmail.com&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Après ça, je n'ai plus qu'à relancer mon application et à provoquer quelques
erreurs pour aussitôt voir ma boite mail se remplir. Heureusement que c'est
pour du test...&lt;/p&gt;
&lt;p&gt;Et pour être complet, il est sans doute préférable d'extraire le paramétrage
du serveur SMTP de la configuration spécifique d'ELMAH et de le définir au
niveau de la section prévue à cet effet dans le Web.config :&lt;/p&gt;
&lt;pre&gt;
&amp;lt;configuration&amp;gt;
  ...
  &amp;lt;elmah&amp;gt;
    &amp;lt;errorMail
       from=&amp;quot;go@gmail.com&amp;quot;
       to=&amp;quot;michel@gmail.com&amp;quot;
       subject=&amp;quot;Application Exception&amp;quot;
       async=&amp;quot;true&amp;quot;
       smtpPort=&amp;quot;0&amp;quot;
       useSsl=&amp;quot;true&amp;quot;&amp;gt;
    &amp;lt;/errorMail&amp;gt;
  &amp;lt;/elmah&amp;gt;
  ...
  &amp;lt;system.net&amp;gt;
    &amp;lt;mailSettings&amp;gt;
      &amp;lt;smtp deliveryMethod =&amp;quot;Network&amp;quot;&amp;gt;
        &amp;lt;network host=&amp;quot;smtp.gmail.com&amp;quot; 
                 port=&amp;quot;587&amp;quot;
                 userName=&amp;quot;go@gmail.com&amp;quot;
                 password=&amp;quot;mot_de_passe&amp;quot; /&amp;gt;
      &amp;lt;/smtp&amp;gt;
    &amp;lt;/mailSettings&amp;gt;
  &amp;lt;/system.net&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Dans ce cas, il faut faire attention aux deux points suivants :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;paramétrer le port &amp;quot;0&amp;quot; au niveau de la section
&lt;code&gt;&amp;lt;errorMail&amp;gt;&lt;/code&gt; pour que ce le port défini au niveau de la
section &lt;code&gt;&amp;lt;network&amp;gt;&lt;/code&gt; soit pris en compte,&lt;/li&gt;
&lt;li&gt;l'activation du SSL se fait toujours dans la section
&lt;code&gt;&amp;lt;errorMail&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;</description>
    
    
    
      </item>
    
  <item>
    <title>Installer ELMAH avec NuGet</title>
    <link>http://blog.pagesd.info/post/2011/09/28/installer-elmah-avec-nuget</link>
    <guid isPermaLink="false">urn:md5:eeeb8bba85afd2d6e293bb97d34aac20</guid>
    <pubDate>Wed, 28 Sep 2011 20:39:00 +0200</pubDate>
    <dc:creator>Michel</dc:creator>
        <category>Elmah</category><category>NuGet</category><category>vs2010</category>    
    <description>    &lt;p&gt;Jusqu'à présent, j'ai un peu tendance à utiliser &lt;a href=&quot;http://nuget.org/&quot; hreflang=&quot;us&quot;&gt;NuGet&lt;/a&gt; uniquement pour mettre à jour Entity Framework, jQuery
et consorts et dans mes très grands jours pour voir ce que donne un package de
ci de là.&lt;/p&gt;
&lt;p&gt;Après avoir terminé le chapitre de &lt;a href=&quot;http://www.amazon.fr/gp/product/1118076583/ref=as_li_qf_sp_asin_tl?ie=UTF8&amp;amp;tag=07arde-21&amp;amp;linkCode=as2&amp;amp;camp=1642&amp;amp;creative=6746&amp;amp;creativeASIN=1118076583&quot; hreflang=&quot;fr&quot;&gt;Professional ASP.NET MVC 3&lt;/a&gt; consacré à NuGet, je me lance et
j'en profite pour installer &lt;a href=&quot;http://code.google.com/p/elmah/&quot; hreflang=&quot;us&quot;&gt;ELMAH&lt;/a&gt;. C'est l'exemple donné dans le livre et c'est un outil que
j'avais déjà essayé d'utiliser du temps de mes WebForms puis de MVC 1.0 mais
que j'avais chaque fois laissé tombé par manque de temps.&lt;/p&gt;
&lt;p&gt;Je vais éviter la console pour l'instant et faire l'installation par un
clic-droit sur la branche &amp;quot;Références&amp;quot; de mon projet puis en sélectionnant
l'option &amp;quot;Manage NuGet Packages...&amp;quot;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://blog.pagesd.info/public/illustrations/elmah01.png&quot; alt=&quot;elmah01.png&quot; style=&quot;display:block; margin:0 auto;&quot; title=&quot;Manage NuGet Packages...&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;ins&gt;Note&lt;/ins&gt; : cette option est également disponible quand on fait
un clic droit sur la solution ou le nom du projet, mais dans ce cas elle plus
difficile à repérer étant donné le plus grand nombre d'options proposées.&lt;/p&gt;
&lt;p&gt;Il apparait alors la fenêtre &amp;quot;Manage NuGet Packages&amp;quot; qui liste par défaut
tous les packages du monde en commençant par les plus téléchargés. Une fois là,
le plus simple est d'utiliser la zone de recherche en haut à droite et d'y
taper &amp;quot;elmah&amp;quot; pour n'afficher que les packages appropriés. Malgré tout, il
reste encore une quinzaine de packages dans la liste !&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://blog.pagesd.info/public/illustrations/elmah02.png&quot; alt=&quot;elmah02.png&quot; style=&quot;display:block; margin:0 auto;&quot; title=&quot;elmah02.png, sept. 2011&quot; /&gt;&lt;/p&gt;
&lt;p&gt;En cliquant sur un package, la colonne de droite est mise à jour et affiche
tout un tas d'informations sur le package, avec entre autre sa version, sa
description et ses dépendances. Comme rien n'est précisé dans le livre,
j'installe le premier résultat obtenu (et donc le plus téléchargé), à savoir
&amp;quot;ELMAH&amp;quot; (tout court) en cliquant sur le bouton &amp;quot;Install&amp;quot; qui apparait
opportunément lorsque la souris le survole. Et c'est parti !&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://blog.pagesd.info/public/illustrations/elmah03.png&quot; alt=&quot;elmah03.png&quot; style=&quot;display:block; margin:0 auto;&quot; title=&quot;elmah03.png, sept. 2011&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;ins&gt;Note&lt;/ins&gt; : si j'avais accédé à la fenêtre &amp;quot;Manage NuGet
Packages&amp;quot; par un clic-droit sur la solution, j'aurai eu droit à un préliminaire
me demandant d'indiquer pour quels projet je souhaitais installer le
package :&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://blog.pagesd.info/public/illustrations/elmah04.png&quot; alt=&quot;elmah04.png&quot; style=&quot;display:block; margin:0 auto;&quot; title=&quot;elmah04.png, sept. 2011&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Une fois que l'installation (très rapide) est terminée, la fenêtre &amp;quot;Manage
NuGet Packages&amp;quot; est rafraichie et je peux constater par une coche verte que le
package &amp;quot;ELMAH&amp;quot; est désormais installé, ainsi que le package &amp;quot;ELMAH Core
Library (no config)&amp;quot; dont il dépend.&lt;/p&gt;
&lt;p&gt;Je peux alors fermer NuGet en cliquant sur le bouton &amp;quot;Close&amp;quot; en bas à gauche
et constater que la librairie &amp;quot;Elmah&amp;quot; est maintenant référencée dans mon
projet.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://blog.pagesd.info/public/illustrations/elmah05.png&quot; alt=&quot;elmah05.png&quot; style=&quot;display:block; margin:0 auto;&quot; title=&quot;elmah05.png, sept. 2011&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Et ce n'est pas tout ! Le fichier Web.config a été modifié pour
incorporer tous les paramètres nécessaires au bon fonctionnement de Elmah&lt;/p&gt;
&lt;pre&gt;
...
&amp;lt;configuration&amp;gt;
  &amp;lt;configSections&amp;gt;
    &amp;lt;sectionGroup name=&amp;quot;elmah&amp;quot;&amp;gt;
      &amp;lt;section name=&amp;quot;security&amp;quot; requirePermission=&amp;quot;false&amp;quot; type=&amp;quot;Elmah.SecuritySectionHandler, Elmah&amp;quot; /&amp;gt;
      &amp;lt;section name=&amp;quot;errorLog&amp;quot; requirePermission=&amp;quot;false&amp;quot; type=&amp;quot;Elmah.ErrorLogSectionHandler, Elmah&amp;quot; /&amp;gt;
      &amp;lt;section name=&amp;quot;errorMail&amp;quot; requirePermission=&amp;quot;false&amp;quot; type=&amp;quot;Elmah.ErrorMailSectionHandler, Elmah&amp;quot; /&amp;gt;
      &amp;lt;section name=&amp;quot;errorFilter&amp;quot; requirePermission=&amp;quot;false&amp;quot; type=&amp;quot;Elmah.ErrorFilterSectionHandler, Elmah&amp;quot; /&amp;gt;
    &amp;lt;/sectionGroup&amp;gt;
...
    &amp;lt;httpModules&amp;gt;
      &amp;lt;add name=&amp;quot;ErrorLog&amp;quot; type=&amp;quot;Elmah.ErrorLogModule, Elmah&amp;quot; /&amp;gt;
      &amp;lt;add name=&amp;quot;ErrorMail&amp;quot; type=&amp;quot;Elmah.ErrorMailModule, Elmah&amp;quot; /&amp;gt;
      &amp;lt;add name=&amp;quot;ErrorFilter&amp;quot; type=&amp;quot;Elmah.ErrorFilterModule, Elmah&amp;quot; /&amp;gt;
    &amp;lt;/httpModules&amp;gt;
    &amp;lt;httpHandlers&amp;gt;
      &amp;lt;add verb=&amp;quot;POST,GET,HEAD&amp;quot; path=&amp;quot;elmah.axd&amp;quot; type=&amp;quot;Elmah.ErrorLogPageFactory, Elmah&amp;quot; /&amp;gt;
    &amp;lt;/httpHandlers&amp;gt;
  &amp;lt;/system.web&amp;gt;
...
  &amp;lt;system.webServer&amp;gt;
    &amp;lt;modules runAllManagedModulesForAllRequests=&amp;quot;true&amp;quot;&amp;gt;
      &amp;lt;add name=&amp;quot;ErrorLog&amp;quot; type=&amp;quot;Elmah.ErrorLogModule, Elmah&amp;quot; preCondition=&amp;quot;managedHandler&amp;quot; /&amp;gt;
      &amp;lt;add name=&amp;quot;ErrorMail&amp;quot; type=&amp;quot;Elmah.ErrorMailModule, Elmah&amp;quot; preCondition=&amp;quot;managedHandler&amp;quot; /&amp;gt;
      &amp;lt;add name=&amp;quot;ErrorFilter&amp;quot; type=&amp;quot;Elmah.ErrorFilterModule, Elmah&amp;quot; preCondition=&amp;quot;managedHandler&amp;quot; /&amp;gt;
    &amp;lt;/modules&amp;gt;
    &amp;lt;handlers&amp;gt;
      &amp;lt;add name=&amp;quot;Elmah&amp;quot; path=&amp;quot;elmah.axd&amp;quot; verb=&amp;quot;POST,GET,HEAD&amp;quot; type=&amp;quot;Elmah.ErrorLogPageFactory, Elmah&amp;quot; preCondition=&amp;quot;integratedMode&amp;quot; /&amp;gt;
    &amp;lt;/handlers&amp;gt;
  &amp;lt;/system.webServer&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Je peux alors lancer l'application et me rendre à l'URL /elmah.axd pour
constater que tout fonctionne sans encombre :&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://blog.pagesd.info/public/illustrations/elmah06.png&quot; alt=&quot;elmah06.png&quot; style=&quot;display:block; margin:0 auto;&quot; title=&quot;elmah06.png, sept. 2011&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Il ne me reste plus qu'à provoquer quelques erreurs :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;accéder à un fichier /readme.txt inexistant&lt;/li&gt;
&lt;li&gt;tester une route /Foo/Bar/1 qui n'existe pas&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Puis retourner à l'adresse /elmah.axd pour m'assurer que ces deux erreurs
ont bien été prises en compte :&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://blog.pagesd.info/public/illustrations/elmah07.png&quot; alt=&quot;elmah07.png&quot; style=&quot;display:block; margin:0 auto;&quot; title=&quot;elmah07.png, sept. 2011&quot; /&gt;&lt;/p&gt;
&lt;p&gt;C'est déjà pas mal pour un début.&lt;/p&gt;
&lt;p&gt;Il faut juste que je revois un peu la configuration pour que tout ça soit
géré de façon plus pérenne et ne plus avoir &amp;quot;&lt;code&gt;This log is provided by the
In-Memory Error Log&lt;/code&gt;&amp;quot;. Et aussi voir comment faire pour recevoir
automatiquement un mél d'Elmah quand un problème survient.&lt;/p&gt;</description>
    
    
    
      </item>
    
  <item>
    <title>Comic strip</title>
    <link>http://blog.pagesd.info/post/2011/09/23/comic-strip</link>
    <guid isPermaLink="false">urn:md5:707db8299ee4a0dbb613abd6e55890a9</guid>
    <pubDate>Fri, 23 Sep 2011 13:47:00 +0200</pubDate>
    <dc:creator>Michel</dc:creator>
        <category>codefirst</category><category>scaffold</category><category>vs2010</category>    
    <description>    &lt;h3&gt;Go !&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;http://blog.pagesd.info/public/illustrations/goscaffold1.png&quot; alt=&quot;Add Controller&quot; title=&quot;C'est ça, créé-moi mon contrôleur&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Splatch !&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;http://blog.pagesd.info/public/illustrations/goscaffold2.png&quot; alt=&quot;Error&quot; title=&quot;Ben c'est cassé ou bien ?&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Wizzzz !&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;http://blog.pagesd.info/public/illustrations/goscaffold3.png&quot; alt=&quot;ContactsModel.cs&quot; title=&quot;Zut ! Ya'class ContactsModel qui sert à rien&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Humpf...&lt;/h3&gt;
&lt;pre&gt;
Unable to retrieve metadata for 'Go.Models.ContactsModel+Group'. The 
type 'Go.Models.ContactsModel+Contact' was not mapped. Check that 
the type has not been explicitly excluded by using the Ignore method 
or NotMappedAttribute data annotation. Verify that the type was 
defined as a class, is not primitive, nested or generic, and does 
not inherit from EntityObject.
&lt;/pre&gt;
&lt;p&gt;(au moins Goggle me ramènera ici la prochaine fois que j'aurai cette
erreur)&lt;/p&gt;</description>
    
    
    
      </item>
    
  <item>
    <title>Utiliser jQuery UI Datepicker avec ASP.NET MVC</title>
    <link>http://blog.pagesd.info/post/2011/06/16/utiliser-jquery-ui-datepicker-avec-asp-net-mvc</link>
    <guid isPermaLink="false">urn:md5:551d4ba6f7cc225c1e54ecf97d79efae</guid>
    <pubDate>Thu, 16 Jun 2011 19:56:00 +0200</pubDate>
    <dc:creator>Michel</dc:creator>
        <category>jQuery</category><category>MVC</category><category>Tutoriel</category>    
    <description>    &lt;p&gt;Normalement, je n'aime pas trop proposer un calendrier pour permettre la
saisie de dates dans un formulaire. Mais comme le &lt;a href=&quot;http://jqueryui.com/demos/datepicker/&quot; hreflang=&quot;us&quot;&gt;widget Datepicker de
jQuery UI&lt;/a&gt; n'est pas trop envahissant ou irritant, j'ai malgré tout décidé
de lui laisser une chance. Et surtout, je vois ça comme un bon moyen d'aborder
les templates Editor et Display de ASP.NET MVC et d'étudier un peu plus
correctement comment les utiliser.&lt;/p&gt;
&lt;h3&gt;Un projet MVC juste pour tester&lt;/h3&gt;
&lt;p&gt;Pour commencer, j'ai échafaudé vite fait une application de gestion de
livres à partir de la classe suivante :&lt;/p&gt;
&lt;pre&gt;
public class Livre
{
    public int ID { get; set; }
    public string Titre { get; set; }
    public DateTime Edition { get; set; }
}
&lt;/pre&gt;
&lt;p&gt;Pour faire vite, j'ai simplement aménagé l'exemple &lt;a href=&quot;http://weblogs.asp.net/scottgu/archive/2011/05/05/ef-code-first-and-data-scaffolding-with-the-asp-net-mvc-3-tools-update.aspx&quot; hreflang=&quot;us&quot;&gt;EF Code First and Data Scaffolding with the ASP.NET MVC 3 Tools
Update&lt;/a&gt; de Scott Guthrie. Après avoir bricolé un lien vers l'action &amp;quot;Index&amp;quot;
de mon nouveau contrôleur &amp;quot;Livres&amp;quot; dans le fichier &amp;quot;_Layout.cshtml&amp;quot;, j'ai pu
accéder à mon écran de création d'un nouveau livre :&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://blog.pagesd.info/public/illustrations/jquery-datepicker-mvc-01.jpg&quot; alt=&quot;jquery-datepicker-mvc-01.jpg&quot; style=&quot;display:block; margin:0 auto;&quot; title=&quot;jquery-datepicker-mvc-01.jpg, juin 2011&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Deux templates DateTime.cshtml&lt;/h3&gt;
&lt;p&gt;J'ai alors pu passer à l'exemple &lt;a href=&quot;http://rachelappel.com/create-user-friendly-date-fields-with-asp.net-mvc-editortemplates-amp-jqueryui&quot; hreflang=&quot;us&quot;&gt;Create user friendly date fields with ASP.NET MVC EditorTemplates
&amp;amp; jQueryUI&lt;/a&gt; donné par Rachel Appel pour créer les deux templates
&amp;quot;DateTime.cshtml&amp;quot; dans les sous-répertoires &amp;quot;DisplayTemplates&amp;quot; et
&amp;quot;EditorTemplates&amp;quot;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;\Views\Shared\DisplayTemplates\DateTime.cshtml&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;
@model DateTime   
@String.Format(&amp;quot;{0:d}&amp;quot;, Model.Date)
&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;\Views\Shared\EditorTemplates\DateTime.cshtml&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;
@model DateTime
@Html.TextBox(&amp;quot;&amp;quot;, String.Format(&amp;quot;{0:d}&amp;quot;, Model.Date.ToShortDateString()))
&lt;/pre&gt;
&lt;p&gt;Ce qui m'a permi d'avoir des dates &amp;quot;propres&amp;quot; (sans avoir l'heure à 00:00:00)
que ce soit en affichage ou en saisie :&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://blog.pagesd.info/public/illustrations/jquery-datepicker-mvc-02.jpg&quot; alt=&quot;jquery-datepicker-mvc-02.jpg&quot; style=&quot;display:block; margin:0 auto;&quot; title=&quot;jquery-datepicker-mvc-02.jpg, juin 2011&quot; /&gt;&lt;/p&gt;
&lt;p&gt;L'avantage, c'est que maintenant je n'ai rien d'autre à faire pour que
toutes les zones de dates existantes ou à venir dans mon application Livres
bénéficient de la même présentation.&lt;/p&gt;
&lt;h3&gt;Ajout de jQuery UI Datepicker&lt;/h3&gt;
&lt;p&gt;Je continue pas à pas le tutoriel de Rachel Appel en référençant la
librairie jQuery UI (et sa CSS) dans mon fichier &amp;quot;_Layout.cshtml&amp;quot; :&lt;/p&gt;
&lt;pre&gt;
&amp;lt;link href=&amp;quot;@Url.Content(&amp;quot;~/Content/themes/base/jquery.ui.all.css&amp;quot;)&amp;quot; rel=&amp;quot;stylesheet&amp;quot; type=&amp;quot;text/css&amp;quot; /&amp;gt;
&amp;lt;script src=&amp;quot;@Url.Content(&amp;quot;~/Scripts/jquery-ui-1.8.11.min.js&amp;quot;)&amp;quot; type=&amp;quot;text/javascript&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Puis je fais évoluer mon EditorTemplate pour ajouter une classe CSS afin que
jQuery puisse identifier les zones de saisie de date :&lt;/p&gt;
&lt;pre&gt;
@model DateTime
@Html.TextBox(&amp;quot;&amp;quot;, String.Format(&amp;quot;{0:d}&amp;quot;, Model.Date.ToShortDateString()), new { @class = &amp;quot;datefield&amp;quot; })
&lt;/pre&gt;
&lt;p&gt;Et je n'ai plus qu'à utiliser la fonction jQuery ready pour indiquer que
tous les éléments qui ont la classe CSS &amp;quot;datefield&amp;quot; doivent être complété d'un
calendrier :&lt;/p&gt;
&lt;pre&gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt;
$(function () {
    $(&amp;quot;.datefield&amp;quot;).datepicker();
});
&amp;lt;/script&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Personnellement, j'ai placé ce script dans mon fichier &amp;quot;_Layout.cshtml&amp;quot;
plutôt que dans le template &amp;quot;DateTime.cshtml&amp;quot; pour éviter qu'il soit répété (et
donc ré-exécuté) autant de fois qu'il y a de date dans mon formulaire de
saisie.&lt;/p&gt;
&lt;p&gt;Et maintenant, quand je suis en saisie d'une date, j'ai le calendrier de
jQuery UI qui apparait :&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://blog.pagesd.info/public/illustrations/jquery-datepicker-mvc-03.jpg&quot; alt=&quot;jquery-datepicker-mvc-03.jpg&quot; style=&quot;display:block; margin:0 auto;&quot; title=&quot;jquery-datepicker-mvc-03.jpg, juin 2011&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Un calendrier en français&lt;/h3&gt;
&lt;p&gt;C'est pas mal, mais c'est tout en anglais :) Heureusement, il y a moyen
d'avoir une version traduite en français très facilement. Il suffit de
récupérer le fichier &amp;quot;jquery.ui.datepicker-fr.js&amp;quot; dans le référentiel Git de
jQuery UI : &lt;a href=&quot;https://github.com/jquery/jquery-ui/blob/master/ui/i18n/&quot; hreflang=&quot;us&quot;&gt;https://github.com/jquery/jquery-ui/blob/master/ui/i18n/&lt;/a&gt; puis de
l'enregistrer dans le répertoire &amp;quot;Scripts&amp;quot; de la solution (et de penser à
l'inclure dans le projet).&lt;/p&gt;
&lt;p&gt;Il ne reste alors plus qu'à référencer ce script (après le script pour
jQuery UI ?) dans le layout :&lt;/p&gt;
&lt;pre&gt;
&amp;lt;script src=&amp;quot;@Url.Content(&amp;quot;~/Scripts/jquery.ui.datepicker-fr.js&amp;quot;)&amp;quot; type=&amp;quot;text/javascript&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Et cerise sur le gâteau, le calendrier est maintenant en mesure de
reconnaitre la date en cours et de s'y positionner correctement :&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://blog.pagesd.info/public/illustrations/jquery-datepicker-mvc-04.jpg&quot; alt=&quot;jquery-datepicker-mvc-04.jpg&quot; style=&quot;display:block; margin:0 auto;&quot; title=&quot;jquery-datepicker-mvc-04.jpg, juin 2011&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Le cas des dates nullables&lt;/h3&gt;
&lt;p&gt;En creusant un peu sur différents exemples d'utilisation du Datepicker de
jQuery UI avec ASP.NET MVC, je suis tombé sur des démos qui allaient un peu
plus loin et qui prenaient en compte le cas où la date était nulle.&lt;/p&gt;
&lt;p&gt;Dans ce cas là, il faut que les templates &amp;quot;Datetime.cshtml&amp;quot; héritent de
l'objet &lt;code&gt;DateTime?&lt;/code&gt; et plus de l'objet &lt;code&gt;DateTime&lt;/code&gt;. Et
donc modifier le code pour gérer le fait qu'on a à faire à un objet nullable,
ce qui au final donne les templates suivants :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;\Views\Shared\DisplayTemplates\DateTime.cshtml&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;
@model System.DateTime?
@(Model.HasValue ? Model.Value.Date.ToShortDateString() : string.Empty)
&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;\Views\Shared\EditorTemplates\DateTime.cshtml&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;
@model System.DateTime?
@Html.TextBox(&amp;quot;&amp;quot;, Model.HasValue ? Model.Value.Date.ToShortDateString() : string.Empty, new { @class = &amp;quot;datefield&amp;quot; })
&lt;/pre&gt;
&lt;p&gt;Pour tester que ça marchait, j'ai dû ajouter un seul &amp;quot;?&amp;quot; à ma classe
&amp;quot;Livre&amp;quot; :&lt;/p&gt;
&lt;pre&gt;
public class Livre
{
    public int ID { get; set; }
    public string Titre { get; set; }
    public DateTime? Edition { get; set; }
}
&lt;/pre&gt;
&lt;p&gt;Et après ça j'ai dû supprimer ma base de données (le fichier
App_Data\Livres.sdf dans mon cas) puis qu'elle avait changé. Chercher &amp;quot;Changing
our Model and Database Schema&amp;quot; sur le billet &lt;a href=&quot;http://weblogs.asp.net/scottgu/archive/2011/01/11/vs-2010-sp1-and-sql-ce.aspx&quot; hreflang=&quot;us&quot;&gt;VS 2010 SP1 and SQL CE&lt;/a&gt; de Scott Guthrie pour plus
d'explications.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note :&lt;/strong&gt; j'ai vu des exemples qui initialisent une valeur par
défaut lorsque la date est nulle (genre &lt;code&gt;Model.HasValue ?
Model.Value.Date.To...() : DateTime.Today.To...()&lt;/code&gt;). Mais selon moi,
ce n'est pas quelque chose qui doit être décidé et accompli au niveau d'un
template. Il est préférable de prévoir ce genre d'initialisation dans une
classe ViewModel.&lt;/p&gt;
&lt;h3&gt;Moderniser le code HTML&lt;/h3&gt;
&lt;p&gt;Le déclenchement du calendrier est basé sur la présence de la classe CSS
&amp;quot;datefield&amp;quot; (ce que fait &lt;code&gt;$(&amp;quot;.datefield&amp;quot;).datepicker();&lt;/code&gt;). Mais
c'est quasiment la préhistoire du &lt;a href=&quot;http://fr.wikipedia.org/wiki/Javascript_discret&quot; hreflang=&quot;fr&quot;&gt;Javascript non
intrusif&lt;/a&gt;. Je pense qu'actuellement, il vaut bien mieux se baser sur
l'attribut &lt;code&gt;type&lt;/code&gt; qui est justement prévu pour définir une &lt;a href=&quot;http://diveintohtml5.org/forms.html#type-date&quot; hreflang=&quot;us&quot;&gt;saisie de date en
HTML5&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Et donc, plutôt que d'ajouter une classe CSS &amp;quot;datefield&amp;quot;, le template Editor
va directement ajouter un attribut &lt;code&gt;type=&amp;quot;date&amp;quot;&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;
@model System.DateTime?
@Html.TextBox(&amp;quot;&amp;quot;, Model.HasValue ? Model.Value.Date.ToShortDateString() : string.Empty, new { @type = &amp;quot;date&amp;quot; })
&lt;/pre&gt;
&lt;p&gt;Il faut alors revoir la fonction jQuery ready pour que désormais elle prenne
en compte les éléments ayant ce type :&lt;/p&gt;
&lt;pre&gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt;
$(function () {
    $(&amp;quot;input[type=date]&amp;quot;).datepicker();
});
&amp;lt;/script&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Et on va même plus loin en utilisant la librairie Modernizr promue par
ASP.NET MVC pour appliquer le calendrier de jQuery UI uniquement lorsque le
navigateur ne prend pas en charge la saisie des dates :&lt;/p&gt;
&lt;pre&gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt;
$(function () {
    if (!Modernizr.inputtypes.date) {
        $(&amp;quot;input[type=date]&amp;quot;).datepicker();
    }
});
&amp;lt;/script&amp;gt;
&lt;/pre&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Finalement, c'est pas hyper compliqué de faire des templates. Et en y
réfléchissant un peu mieux, je pense que c'est une solution qui devrait me
plaire parce qu'elle est totalement &amp;quot;discrète&amp;quot; :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;côté client, le calendrier est appliqué de façon non intrusive : le
code de la balise input n'a pas été affublé d'un &lt;code&gt;onclick&lt;/code&gt; pour lui
attacher un calendrier.&lt;/li&gt;
&lt;li&gt;côté serveur, le calendrier est généré de façon non intrusive : le
formulaire a conservé &lt;code&gt;@Html.EditorFor(model =&amp;gt; model.Edition)&lt;/code&gt;
sans qu'on ait à le défigurer avec un helper spécifique genre
&lt;code&gt;@Html.DateTimeFor(model =&amp;gt; model.Edition)&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;</description>
    
    
    
      </item>
    
  <item>
    <title>Télécharger des fichiers avec Sinatra</title>
    <link>http://blog.pagesd.info/post/2011/06/16/telecharger-fichiers-avec-sinatra</link>
    <guid isPermaLink="false">urn:md5:53bf32a2f739bf6e8cdd6fa53565f929</guid>
    <pubDate>Wed, 15 Jun 2011 19:35:00 +0200</pubDate>
    <dc:creator>Michel</dc:creator>
        <category>Traductions</category>
        <category>Ruby</category><category>Sinatra</category><category>Tutoriel</category>    
    <description>    &lt;div class=&quot;encart&quot;&gt;
&lt;p&gt;Ceci est la traduction du tutoriel &amp;quot;&lt;a href=&quot;http://ididitmyway.heroku.com/past/2011/1/16/uploading_files_in_sinatra/&quot; hreflang=&quot;us&quot;&gt;Uploading Files In Sinatra&lt;/a&gt;&amp;quot; de Darren Jones.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Il arrive fréquemment qu'on ait besoin de télécharger un fichier dans nos
applications, que ce soit une image ou un fichier PDF. Dans ce tutoriel, je
vais vous présenter le code nécessaire pour télécharger des fichiers avec votre
application Sinatra. C'est une solution toute simple, mais ça fait ce qu'on lui
demande.&lt;/p&gt;
&lt;h3&gt;L'application Sinatra&lt;/h3&gt;
&lt;p&gt;Pour commencer, nous avons besoin de créer une application Sinatra basique
avec une page &amp;quot;upload&amp;quot; :&lt;/p&gt;
&lt;pre&gt;
require 'rubygems'
require 'sinatra'

get '/upload' do
  haml :upload
end

__END__

@@upload
%h2 Upload
%form{:action=&amp;gt;&amp;quot;/upload&amp;quot;,:method=&amp;gt;&amp;quot;post&amp;quot;,:enctype=&amp;gt;&amp;quot;multipart/form-data&amp;quot;}
  %input{:type=&amp;gt;&amp;quot;file&amp;quot;,:name=&amp;gt;&amp;quot;file&amp;quot;}
  %input{:type=&amp;gt;&amp;quot;submit&amp;quot;,:value=&amp;gt;&amp;quot;Télécharger&amp;quot;}
&lt;/pre&gt;
&lt;p&gt;(Remarque : je n'ai pas défini de layout pour cet exemple)&lt;/p&gt;
&lt;p&gt;Cette page va afficher un formulaire tout simple qui va nous permettre de
sélectionner un fichier sur notre PC. Le truc le plus important, c'est
l'attribut &amp;quot;enctype&amp;quot; qui doit impérativement être initialisé à
&amp;quot;multipart/form-data&amp;quot;.&lt;/p&gt;
&lt;p&gt;Nous devons ensuite gérer ce qui se passe lorsque le formulaire est validé
en créant un autre handler (à placer juste avant la déclaration
&lt;code&gt;__END__&lt;/code&gt;) :&lt;/p&gt;
&lt;pre&gt;
post '/upload' do
  unless params[:file] &amp;amp;&amp;amp; (tmpfile = params[:file][:tempfile]) &amp;amp;&amp;amp; (name = params[:file][:filename])
    return haml(:upload)
  end
  while blk = tmpfile.read(65536)
    File.open(File.join(Dir.pwd,&amp;quot;public/uploads&amp;quot;, name), &amp;quot;wb&amp;quot;) { |f| f.write(tmpfile.read) }
  end
 'terminé'
end
&lt;/pre&gt;
&lt;p&gt;Etudions un peu ce code pour voir comment ça marche. Après avoir sélectionné
un fichier et cliqué sur le bouton &amp;quot;Télécharger&amp;quot;, un objet &lt;code&gt;file&lt;/code&gt;
est envoyé au serveur via le hash &amp;quot;params&amp;quot;. Cet objet &lt;code&gt;file&lt;/code&gt;
contient une propriété &amp;quot;tempfile&amp;quot; qui correspond au fichier temporaire créé
pendant le téléchargement du fichier. Il contient aussi une propriété
&amp;quot;filemane&amp;quot; qui correspond au nom du fichier tel qu'il était défini sur votre
système. La première ligne de code vérifie que l'on a bien sélectionné un
fichier et utilise les 2 propriétés &amp;quot;tempfile&amp;quot; et &amp;quot;filename&amp;quot; pour initialiser
deux variables du même nom. Dans le cas où aucun fichier n'a été sélectionné,
le formulaire de téléchargement est simplement ré-affiché (ligne 2).&lt;/p&gt;
&lt;p&gt;La boucle &lt;code&gt;while&lt;/code&gt; est alors utilisé pour gérer le contenu du
fichier au fur et à mesure de son chargement. On fait cela par bloc de 65536
octets qui selon moi devrait servir de tampon pour limiter l'utilisation de la
RAM. Ces blocs sont enregistrés dans un fichier dans le répertoire public nommé
&amp;quot;uploads&amp;quot;. Il est nécessaire d'avoir au préalable créé vous-même ce
sous-répertoire &amp;quot;uploads&amp;quot; dans le répertoire &amp;quot;public&amp;quot; de votre application. Le
paramètre &amp;quot;wb&amp;quot; indique à Ruby qu'il s'agit d'un fichier binaire (le &amp;quot;b&amp;quot;) et
qu'il est en écriture seule (le &amp;quot;w&amp;quot;). Vous pouvez retrouver les différentes
valeurs acceptées par de paramètre qui défini le mode du fichier au début de la
&lt;a href=&quot;http://ruby-doc.org/core/classes/IO.html&quot; hreflang=&quot;us&quot;&gt;documentation
de la classe IO de Ruby&lt;/a&gt;. Et pour finir, une fois que le fichier a été
entièrement chargé, on se contente pour l'instant d'afficher un simple message
&amp;quot;terminé&amp;quot;, mais il est bien évident qu'on pourrait faire mieux.&lt;/p&gt;
&lt;p&gt;Pour avoir plus d'informations sur l'objet &lt;code&gt;file&lt;/code&gt; en Ruby, je
vous invite à consulter le chapitre qui est consacré à la classe
&lt;code&gt;File&lt;/code&gt; sur la version en ligne du &lt;a href=&quot;http://ruby-doc.org/docs/ProgrammingRuby/html/ref_c_file.html&quot; hreflang=&quot;us&quot;&gt;Ruby Pickaxe Book&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note du traducteur :&lt;/strong&gt; plutôt que de copier &amp;quot;manuellement&amp;quot; le
contenu du fichier, on aurait pu utiliser la méthode &lt;code&gt;cp&lt;/code&gt; (ou
&lt;code&gt;copy&lt;/code&gt;) de la classe FileUtils :&lt;/p&gt;
&lt;pre&gt;
  fullname = File.join(Dir.pwd, &amp;quot;public/uploads&amp;quot;, name)
  FileUtils.cp(tmpfile.path, fullname)
&lt;/pre&gt;
&lt;h3&gt;Heroku&lt;/h3&gt;
&lt;p&gt;Le code présenté ci-dessus est tout à fait correct et fonctionnel, mais
Heroku ne vous permet pas de stocker beaucoup de fichiers sur leurs serveurs
(seuls les fichiers relatifs au code de votre application devraient s'y
trouver). Par conséquent, vous êtes censés héberger vos fichiers ailleurs. La
solution qui vient à l'esprit, c'est d'utiliser &lt;a href=&quot;http://aws.amazon.com/fr/s3/&quot; hreflang=&quot;fr&quot;&gt;Amazon’s Simple Storage Service
(S3)&lt;/a&gt;. Cela vous permet de stocker vos fichiers sur les serveurs d'Amazon
pour un coût extrêmement minime. Heroku étant également hébergé sur les
serveurs Amazon, en y hébergeant vos propres fichiers vous devriez réduire au
maximum les temps de latence pour récupérer vos fichiers.&lt;/p&gt;
&lt;h3&gt;Inscription à Amazon S3&lt;/h3&gt;
&lt;p&gt;La première chose à faire, c'est de vous enregistrer sur S3, ce que vous
pouvez faire en vous rendant sur le site et en cliquant le bouton
&amp;quot;Inscrivez-vous à Amazon S3&amp;quot;. Si vous êtes déjà un client d'Amazon, vous pouvez
ré-utiliser les informations de votre compte. Une fois que vous avez terminé le
processus d'inscription, vous pouvez commencer à ajouter des fichiers à votre
compte S3. Pour cela, Amazon propose une &lt;a href=&quot;http://aws.amazon.com/s3/us&quot;&gt;console web&lt;/a&gt; ou si vous préférez, vous pouvez
employer &lt;a href=&quot;http://www.s3fox.net/&quot; hreflang=&quot;us&quot;&gt;l'extension S3Fox&lt;/a&gt;
pour Firefox. Amazon S3 utilise un concept de compartiment (bucket) pour gérer
vos fichiers. Et vous pouvez créer des dossiers à l'intérieur de chaque
compartiment. Personnellement, j'ai l'habitude de définir un compartiment pour
chacun de mes sites web.&lt;/p&gt;
&lt;h3&gt;Le code source Sinatra&lt;/h3&gt;
&lt;p&gt;Maintenant que vous disposez d'un compte Amazon S3 en état de marche, vous
allez pouvoir y enregistrer vos fichiers en utilisant son interface web et en
apportant quelques modifications à votre code Sinatra. Pour commence, il faut
installer la &lt;a href=&quot;http://amazon.rubyforge.org/&quot; hreflang=&quot;us&quot;&gt;librairie
aws-s3&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;
C:\Ruby&amp;gt;gem install aws-s3
&lt;/pre&gt;
&lt;p&gt;Lorsque vous vous êtes inscrit à Amazon S3, vous devez avoir reçu deux
informations importante : un Access Key ID de 20 chiffres et une Secret
Access Key. Vous poiuvez les obtenir en vous identifiant sur la page &lt;a href=&quot;http://aws.amazon.com/fr/&quot; hreflang=&quot;fr&quot;&gt;Amazon Web Services&lt;/a&gt; puis en
cliquant sur l'onglet &amp;quot;Compte&amp;quot;. Puis vous devez cliquer sur le lien
&amp;quot;Identification de sécurité&amp;quot;. Les informations dont vous avez besoin
apparaissent dans une boite au milieu de la page (vous devrez cliquer pour
faire apparaitre la clé secrète qui fait 40 caractères).&lt;/p&gt;
&lt;p&gt;Une fois que vous avez récupéré ces informations, vous allez pouvoir la
commande &lt;code&gt;set&lt;/code&gt; de Sinatra pour les enregistrer. Et nous allons en
profiter pour ajouter une instruction &lt;code&gt;require&lt;/code&gt; afin que la
librairie aws-s3 soit inclue dans notre code source :&lt;/p&gt;
&lt;pre&gt;
require 'rubygems'
require 'sinatra'
require 'aws/s3'

set :bucket, 'mybucket'
set :s3_key, THISISANEXAMPLEKEYID
set :s3_secret, Thi$isJu5taNExamp/etO0itSh0u1dBel0NgeR
&lt;/pre&gt;
&lt;p&gt;Bien entendu, pour un site en production il faut définir ces variables en
passant par des paramètres d'environnement de Heroku pour que vos clés restent
secrètes. Vous pouvez vous reporter au billet &lt;a href=&quot;http://blog.pagesd.info/post/2011/05/03/configuration-et-parametrage-avec-sinatra&quot; hreflang=&quot;fr&quot;&gt;Configuration et paramétrage avec Sinatra&lt;/a&gt; pour avoir plus
d'explications à ce sujet.&lt;/p&gt;
&lt;p&gt;Nous devons ensuite nous occuper de nos handlers. Le premier handler pour le
GET reste tel quel alors que le second pour le POST doit être légèrement
modifié de façon à utiliser la librairie aws-s3 pour établir la connexio avec
Amazon S3 et enregistrer le fichier. Vous pouvez voir que nous utilisons pour
cela les variables définies auparavant et qu'à part cela, le code Sinatra final
reste tel qu'il était :&lt;/p&gt;
&lt;pre&gt;
get '/upload' do
  haml :upload
end

post '/upload' do
  unless params[:file] &amp;amp;&amp;amp; (tmpfile = params[:file][:tempfile]) &amp;amp;&amp;amp; (name = params[:file][:filename])
    return haml(:upload)
  end
  while blk = tmpfile.read(65536)
    AWS::S3::Base.establish_connection!(
      :access_key_id     =&amp;gt; settings.s3_key,
      :secret_access_key =&amp;gt; settings.s3_secret)
    AWS::S3::S3Object.store(name,open(tmpfile),settings.bucket,:access =&amp;gt; :public_read)     
  end
 'terminé'
end

__END__

@@upload
%h2 Upload
%form{:action=&amp;gt;&amp;quot;/upload&amp;quot;,:method=&amp;gt;&amp;quot;post&amp;quot;,:enctype=&amp;gt;&amp;quot;multipart/form-data&amp;quot;}
  %input{:type=&amp;gt;&amp;quot;file&amp;quot;,:name=&amp;gt;&amp;quot;file&amp;quot;}
  %input{:type=&amp;gt;&amp;quot;submit&amp;quot;,:value=&amp;gt;&amp;quot;Télécharger&amp;quot;}
&lt;/pre&gt;
&lt;p&gt;Et c'est tout ! Tout cela devrait amplement vous suffire pour gérer les
bases du téléchargement de fichiers avec Sinatra. Il est bien évident que cela
peut être amélioré, notamment dans les domaines suivants :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Un formulaire un peu plus sexy&lt;/li&gt;
&lt;li&gt;Un meilleur retour à la fin du téléchargement (y compris dans le cas où
aucun fichier n'a été sélectionné)&lt;/li&gt;
&lt;li&gt;Une barre de progression au fur et à mesure du téléchargement du
fichier&lt;/li&gt;
&lt;li&gt;La possibilité de télécharger dans différents dossiers&lt;/li&gt;
&lt;li&gt;Un contrôle du type de fichier téléchargé&lt;/li&gt;
&lt;li&gt;Un traitement des images téléchargées (créer une vignette automatiquement
par exemple), bien qu'il s'agisse là d'un sujet qui mérite un billet à lui tout
seul,&lt;/li&gt;
&lt;li&gt;La possibilité de pouvoir télécharger plusieurs fichiers à la fois&lt;/li&gt;
&lt;/ul&gt;</description>
    
    
    
      </item>
    
  <item>
    <title>Utiliser Rack Flash avec Sinatra</title>
    <link>http://blog.pagesd.info/post/2011/06/08/utiliser-rack-flash-avec-sinatra</link>
    <guid isPermaLink="false">urn:md5:b691e6a1c83bb0178ef9c068f3d56436</guid>
    <pubDate>Wed, 08 Jun 2011 19:50:00 +0200</pubDate>
    <dc:creator>Michel</dc:creator>
        <category>Traductions</category>
        <category>Ruby</category><category>Sinatra</category><category>Tutoriel</category>    
    <description>    &lt;div class=&quot;encart&quot;&gt;
&lt;p&gt;Ceci est la traduction du tutoriel Sinatra &amp;quot;&lt;a href=&quot;http://ididitmyway.heroku.com/past/2011/3/15/rack_flash_/&quot; hreflang=&quot;us&quot;&gt;Rack
Flash&lt;/a&gt;&amp;quot; de Darren Jones.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;http://blog.pagesd.info/public/illustrations/the-flash.jpg&quot; alt=&quot;The Flash&quot; style=&quot;display:block; margin:0 auto;&quot; title=&quot;The Flash, mai 2011&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Flash est un tableau hash bien pratique que l'on trouve dans Rails. Il ne
s'agit pas du super héros qui se déplace à la vitesse de l'éclair mais d'une
astuce pour stocker temporairement des informations entre deux requêtes HTTP.
Si par exemple votre application enregistre une nouvelle fiche puis redirige
l'utilisateur vers une autre page, vous pouvez utiliser cette mémoire flash
pour stocker un message indiquant que la fiche a bien été enregistrée. Ce
message sera ensuite affiché sur la nouvelle page (après le redirect).&lt;/p&gt;
&lt;p&gt;On peut avoir la même fonctionnalité avec Sinatra en s'appuyant sur &lt;a href=&quot;http://nakajima.github.com/rack-flash/&quot; hreflang=&quot;fr&quot;&gt;Rack Flash&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Tout ce qu'il y a à faire, c'est d'installer le gem :&lt;/p&gt;
&lt;pre&gt;
C:\Ruby&amp;gt;gem install rack-flash
&lt;/pre&gt;
&lt;p&gt;Puis ajouter les quelques lignes suivantes dans notre application
Sinatra :&lt;/p&gt;
&lt;pre&gt;
require 'rack-flash'
use Rack::Flash
enable :sessions
&lt;/pre&gt;
&lt;p&gt;(Rack Flash a besoin des sessions pour stocker le tableau hash et par défaut
elles sont désactivées dans Sinatra)&lt;/p&gt;
&lt;p&gt;Pour utiliser le hash &amp;quot;flash&amp;quot;, il suffit par exemple de définir le message
dans le handler de départ :&lt;/p&gt;
&lt;pre&gt;
post '/notes/save' do
  flash[:notice] = &amp;quot;Votre fiche a été enregistrée&amp;quot;
  redirect '/'
end
&lt;/pre&gt;
&lt;p&gt;Puis il faut afficher ce message dans la vue correspondant au handler pour
l'URL &amp;quot;/&amp;quot; :&lt;/p&gt;
&lt;pre&gt;
&amp;lt;div id='flash' class='notice'&amp;gt;
  &amp;lt;%= flash[:notice] %&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Et maintenant, quand quelqu'un enregistre une note, il verra le message
&amp;quot;Votre fiche a été enregistrée&amp;quot; après avoir été redirigé sur la page index
&amp;quot;/&amp;quot;.&lt;/p&gt;
&lt;p&gt;Vous pouvez associer une clé au message flash pour faire la distinction
entre différents types de message, comme par exemple :&lt;/p&gt;
&lt;pre&gt;
flash[:notice]
flash[:warning]
flash[:error]
&lt;/pre&gt;
&lt;p&gt;Si vous souhaitez afficher le flash dans la requête en cours, vous devez
utiliser flash.now :&lt;/p&gt;
&lt;pre&gt;
get '/' do flash.now:notice = &amp;quot;Pas de message&amp;quot; unless flash:notice end
&lt;/pre&gt;
&lt;p&gt;Cela affichera un message indiquant qu'il n'y a pas de message stocké dans
le hash flash par la requête précédente.&lt;/p&gt;
&lt;p&gt;Les messages restent dans le tableau hash tant qu'ils ne sont pas affichés,
comme vous pouvez le voir dans le code ci-dessous :&lt;/p&gt;
&lt;pre&gt;
get '/un' do
  flash[:notice] = &amp;quot;Coucou !&amp;quot;
  flash[:error] = &amp;quot;erreur de type 1&amp;quot;
  flash[:warning] =  &amp;quot;Attention au chien&amp;quot;
  redirect '/deux' 
end

get '/deux' do
  flash[:error] = &amp;quot;erreur de type 2&amp;quot;
  redirect '/trois' 
end

get '/trois' do
  flash[:notice] = &amp;quot;Salut !&amp;quot;
  redirect '/' 
end

get '/' do
  erb :index
end
&lt;/pre&gt;
&lt;p&gt;Lorsque nous atteindrons enfin l'URL &amp;quot;/&amp;quot; et que la vue &amp;quot;index&amp;quot; sera
affichée, on aura flash[:error] = &amp;quot;erreur de type 2&amp;quot;, flash[:notice] = &amp;quot;Salut
!&amp;quot; et flash[:warning] sera toujours &amp;quot;Attention au chien&amp;quot;.&lt;/p&gt;
&lt;p&gt;Si vous ne voulez pas que les messages s'éternisent dans le hash flash
jusqu'à ce qu'ils soient utilisé, vous pouvez vous en débarrasser en utilisant
l'option &amp;quot;sweep&amp;quot; :&lt;/p&gt;
&lt;pre&gt;
use Rack::Flash, :sweep =&amp;gt; true
&lt;/pre&gt;
&lt;p&gt;Grace à elle, le hash flash est nettoyé après chaque requête, que les
messages aient été affichés ou non. Dans l'exemple précédent, le seul élément
qui restera dans le hash sera flash[:notice] = &amp;quot;Salut !&amp;quot;.&lt;/p&gt;
&lt;p&gt;Et pour finir, un bout de code Erb prêt à insérer dans votre layout pour
afficher un message flash lorsqu'il en existe, en définissant une classe CSS
qui dépend du type de message :&lt;/p&gt;
&lt;pre&gt;
&amp;lt;% flash.each do |key,msg| %&amp;gt;
  &amp;lt;div id='flash' class='&amp;lt;%= key %&amp;gt;'&amp;gt;
    &amp;lt;%= msg %&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;% end %&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Ou si vous préférez le Haml :&lt;/p&gt;
&lt;pre&gt;
- flash.each do |key,msg|
  #message{:class =&amp;gt; key}= msg
&lt;/pre&gt;
&lt;p&gt;Cela a pour effet de créer une balise &amp;lt;div&amp;gt; pour chaque message stocké
dans le hash flash, en utilisant sa clé comme nom de classe CSS.&lt;/p&gt;
&lt;p&gt;En espérant que ça puisse vous servir : la plupart des applications
utilise cette technique, alors n'hésitez pas à l'essayer dans vos applications
Sinatra.&lt;/p&gt;</description>
    
    
    
      </item>
    
  <item>
    <title>Authentification super simple avec Sinatra</title>
    <link>http://blog.pagesd.info/post/2011/05/17/authentification-super-simple-avec-sinatra</link>
    <guid isPermaLink="false">urn:md5:bb8d678b7cc9a6da4682f4b61af3f90e</guid>
    <pubDate>Tue, 17 May 2011 19:33:00 +0200</pubDate>
    <dc:creator>Michel</dc:creator>
        <category>Traductions</category>
        <category>Ruby</category><category>Sinatra</category><category>Tutoriel</category>    
    <description>    &lt;div class=&quot;encart&quot;&gt;
&lt;p&gt;Ceci est la traduction du tutoriel &amp;quot;&lt;a href=&quot;http://ididitmyway.heroku.com/past/2011/2/22/really_simple_authentication_in_sinatra/&quot; hreflang=&quot;us&quot;&gt;Really Simple Authentication in Sinatra&lt;/a&gt;&amp;quot; de Darren Jones.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Une demande répétée des lecteurs de &lt;a href=&quot;http://ididitmyway.heroku.com/&quot; hreflang=&quot;us&quot;&gt;I Dit It My Way&lt;/a&gt; est un billet sur l'authentification. Il
existe des tas de gems qui permettent de faire ça, mais je préfère programmer
ça par moi-même. Voici donc une méthode vraiment simple pour construire le côté
administration d'un site web avec Sinatra.&lt;/p&gt;
&lt;p&gt;Pour démarrer, il faut créer quelques paramètres :&lt;/p&gt;
&lt;pre&gt;
set :username,'michel'
set :password,'topsecret'
set :token,'1truklong&amp;amp;complike@$e$ouven!r'
&lt;/pre&gt;
&lt;p&gt;Puis les handlers pour les différentes routes. En commençant par l'URL
&amp;quot;/admin&amp;quot; pour pouvoir se connecter.&lt;/p&gt;
&lt;pre&gt;
get('/admin') { haml :admin }
&lt;/pre&gt;
&lt;p&gt;Ca ne prend qu'une ligne parce que tout ce qu'il y a à faire c'est
d'afficher la vue &amp;quot;admin.haml&amp;quot; :&lt;/p&gt;
&lt;pre&gt;
%form(action=&amp;quot;/login&amp;quot; method=&amp;quot;post&amp;quot;)
  %label(for=&amp;quot;username&amp;quot;)Code utilisateur :
  %input#username(type=&amp;quot;text&amp;quot; name=&amp;quot;username&amp;quot;)
  %label(for=&amp;quot;password&amp;quot;)Mot de passe :
  %input#password(type=&amp;quot;password&amp;quot; name=&amp;quot;password&amp;quot;)
  %input(type=&amp;quot;submit&amp;quot; value=&amp;quot;Connexion&amp;quot;) or &amp;lt;a href=&amp;quot;/&amp;quot;&amp;gt;Annuler&amp;lt;/a&amp;gt;
&lt;/pre&gt;
&lt;p&gt;C'est un formulaire tout simple qui contient deux zones de texte pour saisir
le code utilisateur et le mot de passe plus un bouton pour valider. Ce
formulaire est renvoyé à l'aide d'une requête POST vers l'URL &amp;quot;/login&amp;quot; et nous
devons donc ajouter un handler pour la traiter. C'est à ce niveau que nous
allons accomplir le plus gros du travail pour authentifier l'utilisateur.&lt;/p&gt;
&lt;pre&gt;
post '/login' do
  if params['username'] == settings.username &amp;amp;&amp;amp; params['password'] == settings.password
      response.set_cookie(settings.username,settings.token) 
      redirect '/'
    else
      &amp;quot;Code utilisateur ou mot de passe incorrect&amp;quot;
    end
end
&lt;/pre&gt;
&lt;p&gt;Ce code vérifie tout d'abord si le code utilisateur et le mot de passe saisi
via le formulaire (stockés dans le hash params&lt;a href=&quot;http://blog.pagesd.info/post/2011/05/17/&quot;&gt;&lt;/a&gt;) correspondent à
ceux définis dans le paramétrage (les valeurs settings.xxxxx). Si c'est le cas,
on crée un cookie en utilisant le code utilisateur comme clé et pour la valeur
le &amp;quot;token&amp;quot; qu'on avait configuré au tout début. On pourrait se contenter
d'utiliser &amp;quot;true&amp;quot; pour la valeur, mais le fait d'utiliser un token renforce le
niveau de sécurité (si quelqu'un sait expliquer comment ?). Lorsque
l'utilisateur n'a pas donné le bon code utilisateur ou le bon mot de passe, on
renvoie simplement un message d'avertissement.&lt;/p&gt;
&lt;p&gt;Et maintenant, tout ce qu'il nous reste à faire c'est de donner la
possibilité de se déconnecter. Ce que nous allons faire en gérant l'URL
&amp;quot;/logout&amp;quot;.&lt;/p&gt;
&lt;pre&gt;
get('/logout') { response.set_cookie(settings.username, false) ; redirect '/' }
&lt;/pre&gt;
&lt;p&gt;Là on modifie le cookie correspondant à l'utilisateur en lui donnant la
valeur &amp;quot;false&amp;quot; puis on le renvoie à la racine du site.&lt;/p&gt;
&lt;p&gt;On a donc géré tout ce qui concerne l'authentification d'un utilisateur. Il
nous reste à traiter la partie autorisation pour réellement autoriser ou
interdire l'accès à certaines pages. Pour cela, on va simplement ajouter deux
fonctions helper à notre code Ruby :&lt;/p&gt;
&lt;pre&gt;
helpers do
  def admin? ; request.cookies[settings.username] == settings.token ; end
  def protected! ; halt [ 401, 'Not Authorized' ] unless admin? ; end
end
&lt;/pre&gt;
&lt;p&gt;Le premier helper, nommé &amp;quot;admin?&amp;quot;, va nous servir pour vérifier si
l'utilisateur est connecté ou non, en contrôlant que la valeur du cookie
correspond à la valeur configurée pour le token. On pourra l'utiliser cet
helper dans le code des handlers ou des vues pour par exemple afficher un
message différent selon que l'utilisateur est connecté ou non.&lt;/p&gt;
&lt;p&gt;Le second helper, nommé &amp;quot;protected!&amp;quot;, utilise le premier helper pour
vérifier si l'utilisateur est connecté et si ce n'est pas le cas il interrompt
le code te renvoie une erreur HTTP 401 pour indiquer que l'action n'est pas
autorisée. On l'utilisera au début du handler d'une route pour indiquer que
l'utilisateur doit être connecté pour visualiser ce contenu.&lt;/p&gt;
&lt;p&gt;Et pour finir le code source complet d'une application qui regroupe tout le
code présenté ci-dessus. Libre à vous de à l'utiliser et de faire vos propres
essais avec.&lt;/p&gt;
&lt;pre&gt;
require 'rubygems'
require 'sinatra'

set :username,'Bond'
set :password,'007'
set :token,'osh@kerp@$@l@cuill3re'

helpers do
  def admin? ; request.cookies[settings.username] == settings.token ; end
  def protected! ; halt [ 401, 'Not Authorized' ] unless admin? ; end
end

get '/' do
  haml :index
end

get('/admin') { haml :admin }

post '/login' do
  if params['username'] == settings.username &amp;amp;&amp;amp; params['password'] == settings.password
    response.set_cookie(settings.username,settings.token) 
    redirect '/'
  else
      &amp;quot;Code utilisateur ou mot de passe incorrect&amp;quot;
  end
end

get('/logout') { response.set_cookie(settings.username, false) ; redirect '/' }

get '/public' do
  'Tout le monde peut voir ça'
end

get '/private' do
  protected!
  'Rien que pour vos yeux !'
end

__END__
@@layout
!!! 5
%html
  %head
    %meta(charset=&amp;quot;utf-8&amp;quot;)
    %title Authentication Super Simple
  %body
    %a(href='/admin')Connexion
    %a(href='/logout')Déconnexion
    %a(href='/public')Public
    %a(href='/private')Privé
    = yield
@@admin
%form(action=&amp;quot;/login&amp;quot; method=&amp;quot;post&amp;quot;)
  %label(for=&amp;quot;username&amp;quot;)Code utilisateur :
  %input#username(type=&amp;quot;text&amp;quot; name=&amp;quot;username&amp;quot;)
  %label(for=&amp;quot;password&amp;quot;)Mot de passe :
  %input#password(type=&amp;quot;password&amp;quot; name=&amp;quot;password&amp;quot;)
  %input(type=&amp;quot;submit&amp;quot; value=&amp;quot;Connexion&amp;quot;) or &amp;lt;a href=&amp;quot;/&amp;quot;&amp;gt;Cancel&amp;lt;/a&amp;gt;
@@index
-if admin?
  %h1 Bienvenue 007 !
-else
  %h1 Bienvenue !
&lt;/pre&gt;
&lt;p&gt;C'est un exemple simple et sans prétention si vous souhaitez expérimenter un
système d'authentification et d'autorisations sous Sinatra. Et si vous avez des
idées pour le faire évoluer ou l'améliorer, n'hésitez pas à en faire part dans
les commentaires ci-dessous.&lt;/p&gt;</description>
    
    
    
      </item>
    
  <item>
    <title>Erreurs 404 et autres avec Sinatra</title>
    <link>http://blog.pagesd.info/post/2011/05/04/erreurs-404-et-autres-avec-sinatra</link>
    <guid isPermaLink="false">urn:md5:5471eb265e05f21560aff8270515a979</guid>
    <pubDate>Wed, 04 May 2011 19:06:00 +0200</pubDate>
    <dc:creator>Michel</dc:creator>
        <category>Traductions</category>
        <category>Ruby</category><category>Sinatra</category><category>Tutoriel</category>    
    <description>    &lt;div class=&quot;encart&quot;&gt;
&lt;p&gt;Ceci est la traduction du tutoriel &amp;quot;&lt;a href=&quot;http://ididitmyway.heroku.com/past/2010/11/16/404_and_other_errors_in_sinatra/&quot; hreflang=&quot;us&quot;&gt;404 and other Errors in Sinatra&lt;/a&gt;&amp;quot; de Darren Jones.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Un truc super bien quand on débute avec Sinatra c'est sa page
d'erreur :&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://blog.pagesd.info/public/Sinatra/reverse-2.png&quot; alt=&quot;Erreur 404&quot; style=&quot;display:block; margin:0 auto;&quot; title=&quot;Sinatra doesn't know this ditty.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Les applications web ont toutes besoin d'afficher des pages d'erreurs et
c'est quelque chose de très simple à faire avec Sinatra.&lt;/p&gt;
&lt;h3&gt;Les erreurs 404&lt;/h3&gt;
&lt;p&gt;Elles se produisent lorsque l'URL appelée ne peut pas être trouvée. On peut
gérer cette erreur à l'aide du code Ruby suivant :&lt;/p&gt;
&lt;pre&gt;
not_found do
  &amp;quot;Votre page n'a pas pu être trouvée&amp;quot;
end
&lt;/pre&gt;
&lt;p&gt;Cela affichera tout simplement le message &amp;quot;Votre page n'a pas pu être
trouvée&amp;quot;. On peut tout de même avoir quelque chose d'un peu plus sophistiqué et
employer une vue &amp;quot;not_found.erb&amp;quot; qui aura l'avantage d'être rendue avec le
&amp;quot;layout.erb&amp;quot; habituel.&lt;/p&gt;
&lt;pre&gt;
not_found do
  erb :not_found
end
&lt;/pre&gt;
&lt;p&gt;On peut même faire tenir ça sur une seule ligne :&lt;/p&gt;
&lt;pre&gt;
not_found { erb :not_found }
&lt;/pre&gt;
&lt;p&gt;Si vous préférez appeler votre fichier &amp;quot;404.erb&amp;quot; au lieu de &amp;quot;not_found.erb&amp;quot;,
vous devrez simplement le mettre entre apostrophe étant donné que les symboles
ne peuvent pas commencer par un chiffre :&lt;/p&gt;
&lt;pre&gt;
not_found do
  erb :'404'
end
&lt;/pre&gt;
&lt;h3&gt;Les erreurs 500&lt;/h3&gt;
&lt;p&gt;Ces erreurs surviennent en cas d'erreur interne du serveur, généralement
lorsque quelque chose a planté dans l'application. Le code suivant sert pour
gérer ce genre d'erreur :&lt;/p&gt;
&lt;pre&gt;
error do
  @error = request.env['sinatra_error']
  haml :'500'
end
&lt;/pre&gt;
&lt;p&gt;Ce code initialise une variable d'instance nommée &lt;code&gt;@error&lt;/code&gt; qu'il
sera possible d'utiliser dans la vue pour détailler l'erreur à l'aide des
méthodes &lt;code&gt;@error.name&lt;/code&gt; et &lt;code&gt;@error.message&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Il est aussi possible de créer des erreurs personnalisées en utilisant le
code suivant :&lt;/p&gt;
&lt;pre&gt;
error BigError do
   &amp;quot;BOUM ! Il y a eu un gros souci ! &amp;quot; + request.env['sinatra.error'].message
end
&lt;/pre&gt;
&lt;p&gt;On peut alors renvoyer cette erreur avec un message adéquat :&lt;/p&gt;
&lt;pre&gt;
get '/' do
  raise BigError, 'Est-ce que vous aviez convenablement fermé toutes vos accolades ?'
end
&lt;/pre&gt;
&lt;p&gt;L'accès à l'URL &amp;quot;/&amp;quot; provoquera alors l'affichage du message
suivant :&lt;/p&gt;
&lt;pre&gt;
BOUM ! Il y a eu un gros souci ! Est-ce que vous aviez convenablement fermé toutes vos accolades ?
&lt;/pre&gt;
&lt;p&gt;Selon moi, il est important de gérer les erreurs dès le tout début du
projet. Pour cela, je colle généralement les deux lignes suivantes au début de
mon fichier :&lt;/p&gt;
&lt;pre&gt;
not_found { haml :'404' }
error { @error = request.env['sinatra_error'] ; haml :'500' }
&lt;/pre&gt;</description>
    
    
    
      </item>
    
  <item>
    <title>Configuration et paramétrage avec Sinatra</title>
    <link>http://blog.pagesd.info/post/2011/05/03/configuration-et-parametrage-avec-sinatra</link>
    <guid isPermaLink="false">urn:md5:4c6703060a1354d47ce9d696712f2d30</guid>
    <pubDate>Tue, 03 May 2011 19:37:00 +0200</pubDate>
    <dc:creator>Michel</dc:creator>
        <category>Traductions</category>
        <category>Ruby</category><category>Sinatra</category><category>Tutoriel</category>    
    <description>    &lt;div class=&quot;encart&quot;&gt;
&lt;p&gt;Ceci est la traduction du tutoriel &amp;quot;&lt;a href=&quot;http://ididitmyway.heroku.com/past/2010/11/9/sinatra_settings_and_configuration/&quot; hreflang=&quot;us&quot;&gt;Sinatra Settings and Configuration&lt;/a&gt;&amp;quot; de Darren Jones.&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Définir des paramètres&lt;/h3&gt;
&lt;p&gt;Pour définir des variables session d'application qui seront accessible
partout dans une application, on peut utiliser la commande &lt;code&gt;set&lt;/code&gt; en
respectant la syntaxe suivante :&lt;/p&gt;
&lt;pre&gt;
set :name, 'MonSite'
set :author, 'Michel'
&lt;/pre&gt;
&lt;p&gt;Ces variables sont ensuite utilisables dans notre code Ruby sous la forme
&lt;code&gt;settings.name&lt;/code&gt; et &lt;code&gt;settings.author&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;
get '/hello' do
  &amp;quot;Cette page a été rédigée par &amp;quot; + settings.author
end
&lt;/pre&gt;
&lt;p&gt;Puis en se connectant à l'URL &amp;quot;/hello&amp;quot; on obtient alors :&lt;/p&gt;
&lt;pre&gt;
Cette page a été rédigée par Michel.
&lt;/pre&gt;
&lt;p&gt;On peut également utiliser la commande &lt;code&gt;set&lt;/code&gt; pour modifier les
paramètres de certains gems, comme par exemple :&lt;/p&gt;
&lt;pre&gt;
set :haml, { :format =&amp;gt; :html5 }
&lt;/pre&gt;
&lt;p&gt;Grâce à quoi Haml génèrera du code HTML5 avec le doctype correct, pas de
barre fermante pour la balise image, etc...&lt;/p&gt;
&lt;p&gt;Il est même possible de modifier les paramètres par défaut de Sinatra. C'est
ce qui permet par exemple de redéfinir le répertoire &amp;quot;public&amp;quot; :&lt;/p&gt;
&lt;pre&gt;
set :public, Proc.new { root }
&lt;/pre&gt;
&lt;p&gt;De cette façon, le répertoire &amp;quot;public&amp;quot; sera identique au répertoire racine
du site.&lt;/p&gt;
&lt;p&gt;Et pour modifier le répertoire où sont enregistrées les vues :&lt;/p&gt;
&lt;pre&gt;
set :views, Proc.new { File.join(root, &amp;quot;templates&amp;quot;) }
&lt;/pre&gt;
&lt;p&gt;Après cela, Sinatra recherchera dans le dossier &amp;quot;templates&amp;quot; pour tous les
fichiers de vues de notre application (en lieu et place du répertoire &amp;quot;views&amp;quot;
utilisé par défaut).&lt;/p&gt;
&lt;p&gt;Quand le paramètre correspond à un booléen, il est possible d'utiliser les
commandes &lt;code&gt;enable&lt;/code&gt; et &lt;code&gt;disable&lt;/code&gt; plutôt que la commande
&lt;code&gt;set&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;
enable :sessions
disable :twitter
&lt;/pre&gt;
&lt;p&gt;Ce qui est exactement identique à :&lt;/p&gt;
&lt;pre&gt;
set :sessions, true
set :twitter, false
&lt;/pre&gt;
&lt;p&gt;La &lt;a href=&quot;http://www.sinatrarb.com/configuration.html&quot; hreflang=&quot;us&quot;&gt;documentation officielle de Sinatra&lt;/a&gt; fourni d'autres explications et
des exemple, entre autre la liste des paramètres internes de Sinatra.&lt;/p&gt;
&lt;h3&gt;Les blocs de configuration&lt;/h3&gt;
&lt;p&gt;Sinatra permet de regrouper un ensemble de paramètres dans un bloc de
configuration :&lt;/p&gt;
&lt;pre&gt;
configure do
  set :name, 'michel'
  set :haml, { :format =&amp;gt; :html5 }
end
&lt;/pre&gt;
&lt;p&gt;Cela va initialiser tous les paramètres ainsi que tout ce qui est nécessaire
au démarrage.&lt;/p&gt;
&lt;p&gt;Et cerise sur le gâteau, on peut définir un bloc de configuration pour
chaque environnement :&lt;/p&gt;
&lt;pre&gt;
configure :development do
  set :db, File.join(&amp;quot;sqlite3://&amp;quot;,settings.root, &amp;quot;development.db&amp;quot;
end

configure :test do
  set :db, File.join(&amp;quot;sqlite3://&amp;quot;,settings.root, &amp;quot;test.db&amp;quot;
end

configure :production do
  File.join(&amp;quot;sqlite3://&amp;quot;,settings.root, &amp;quot;production.db&amp;quot;
  set :sass, { :style =&amp;gt; :compressed }
end
&lt;/pre&gt;
&lt;p&gt;Comme vous pouvez le voir, j'ai configuré Sass pour que sa sortie soit
compressée en production et que le fichier généré soit le plus petit possible.
Il existe tout un tas d'autres options pour &lt;a href=&quot;http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#options&quot; hreflang=&quot;us&quot;&gt;Sass&lt;/a&gt; et &lt;a href=&quot;http://haml-lang.com/docs/yardoc/file.HAML_REFERENCE.html#options&quot; hreflang=&quot;us&quot;&gt;Haml&lt;/a&gt; qui peuvent être initialisées de cette façon.&lt;/p&gt;
&lt;h3&gt;Gérer des paramètres d'environnement sur Heroku&lt;/h3&gt;
&lt;p&gt;Pour finir, on a la possibilité de définir des variables d'environnement
directement sur Heroku, ce qui nous évite d'avoir à rendre la valeur de
certains paramètres publics. Supposons que nous ayons le code
ci-dessous :&lt;/p&gt;
&lt;pre&gt;
set :password, 't0psecret'
&lt;/pre&gt;
&lt;p&gt;Le hic c'est que si on compte partager notre code, les autres personnes
auront accès à notre mot de passe top secret ! La solution dans ce cas là
c'est de définir une variable d'environnement appelée &amp;quot;PASSWORD&amp;quot;, chose de très
simple à faire depuis une console bash. Il suffit de se placer dans le
répertoire de l'application et d'entrer le code ci-dessous :&lt;/p&gt;
&lt;pre&gt;
$ heroku config:add PASSWORD=t0psecret
&lt;/pre&gt;
&lt;p&gt;Ce qui doit renvoyer le message suivant :&lt;/p&gt;
&lt;pre&gt;
Adding config vars:
  PASSWORD    =&amp;gt; t0psecret
Restarting app...done.
&lt;/pre&gt;
&lt;p&gt;Il faut utiliser la commande &lt;code&gt;config&lt;/code&gt; seule pour lister toutes
les variables d'environnement existantes sur Heroku :&lt;/p&gt;
&lt;pre&gt;
$ heroku config
&lt;/pre&gt;
&lt;p&gt;Comme on peut le voir suite à cette commande, Heroku défini déjà par
lui-même un certain nombre de variables d'environnement de façon automatique
dans le but de nous faciliter la vie :&lt;/p&gt;
&lt;pre&gt;
PASSWORD =&amp;gt; t0psecret
DATABASE_URL =&amp;gt; postgres://ibzju...s.com/ibzjubamts
RACK_ENV     =&amp;gt; production
URL          =&amp;gt; http://bloggl.heroku.com
&lt;/pre&gt;
&lt;p&gt;Si on a besoin de se débarrasser de toutes les variables de configuration,
on peut employer la commande suivante :&lt;/p&gt;
&lt;pre&gt;
$ heroku config:clear
&lt;/pre&gt;
&lt;p&gt;Ce que Heroku confirmera avant de redémarrer l'application :&lt;/p&gt;
&lt;pre&gt;
Clearing all config vars and restarting app...done.
&lt;/pre&gt;
&lt;p&gt;Après avoir créé nos variables d'environnement sur Heroku, il ne nous reste
plus qu'à modifier notre code source pour en tirer parti :&lt;/p&gt;
&lt;pre&gt;
set :password, ENV['PASSWORD'] || 'secret'
&lt;/pre&gt;
&lt;p&gt;Ce code va utiliser la variable d'environnement enregistrée dans
ENV['PASSWORD'] si elle est définie ou sinon se contenter du simple mot de
passe &amp;quot;secret&amp;quot; défini en dur dans le code. Grâce à cette méthode vous ne
risquez pas de divulguer quoique ce soit d'important ou de personnel dans votre
code source Ruby.&lt;/p&gt;
&lt;p&gt;Pour être complet, le Dev Center d'Heroku présente d'autres informations
concernant l'&lt;a href=&quot;http://devcenter.heroku.com/articles/config-vars&quot; hreflang=&quot;us&quot;&gt;utilisation des variables de configuration sur Heroku&lt;/a&gt;.&lt;/p&gt;</description>
    
    
    
      </item>
    
  <item>
    <title>Un formulaire de contact avec Sinatra</title>
    <link>http://blog.pagesd.info/post/2011/04/29/un-formulaire-de-contact-avec-Sinatra</link>
    <guid isPermaLink="false">urn:md5:5308c6d0f466c2d66e8c64d90398873e</guid>
    <pubDate>Fri, 29 Apr 2011 13:22:00 +0200</pubDate>
    <dc:creator>Michel</dc:creator>
        <category>Traductions</category>
        <category>Ruby</category><category>Sinatra</category><category>Tutoriel</category>    
    <description>    &lt;div class=&quot;encart&quot;&gt;
&lt;p&gt;Ceci est la traduction du tutoriel &amp;quot;&lt;a href=&quot;http://ididitmyway.heroku.com/past/2010/12/4/an_email_contact_form_in_sinatra/&quot; hreflang=&quot;us&quot;&gt;An Email Contact Form in Sinatra&lt;/a&gt;&amp;quot; de Darren Jones.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Dans cet épisode, je vais vous montrer les étapes nécessaires pour créer une
page avec un formulaire de contact qui vous enverra un email. Cet exemple peut
facilement être adapté pour envoyer tout autre type d'email à partir d'une
interface web. Dans ce tutoriel, je vais utiliser mon compte GMail pour
réaliser l'envoi du mail.&lt;/p&gt;
&lt;p&gt;Nous utiliserons également la librairie &lt;a href=&quot;https://github.com/benprew/pony&quot; hreflang=&quot;us&quot;&gt;Pony&lt;/a&gt; pour envoyer les mails
et il faut donc commencer par installer le gem Pony :&lt;/p&gt;
&lt;pre&gt;
C:\Ruby&amp;gt;gem install pony
&lt;/pre&gt;
&lt;p&gt;Pour démarrer, nous allons nous occuper du formulaire de contact. Celui-ci
sera disponible à l'URL &amp;quot;/contact&amp;quot; grâce au gestionnaire suivant :&lt;/p&gt;
&lt;pre&gt;
get '/contact' do
  erb :contact
end
&lt;/pre&gt;
&lt;p&gt;Puis nous enregistrons la vue &amp;quot;contact.erb&amp;quot; dans le répertoire
&amp;quot;views&amp;quot; :&lt;/p&gt;
&lt;pre&gt;
&amp;lt;p&amp;gt;Vous pouvez utiliser le formulaire ci-dessous pour nous contacter :&amp;lt;/p&amp;gt;
&amp;lt;form action=&amp;quot;/contact&amp;quot; method=&amp;quot;post&amp;quot;&amp;gt;
  &amp;lt;label for=&amp;quot;name&amp;quot;&amp;gt;Votre nom :&amp;lt;/label&amp;gt;
  &amp;lt;input type=&amp;quot;text&amp;quot; name=&amp;quot;name&amp;quot;&amp;gt;
  &amp;lt;label for=&amp;quot;email&amp;quot;&amp;gt;Votre adresse mél :&amp;lt;/label&amp;gt;
  &amp;lt;input type=&amp;quot;text&amp;quot; name=&amp;quot;email&amp;quot;&amp;gt;
  &amp;lt;label for=&amp;quot;to&amp;quot;&amp;gt;Votre message :&amp;lt;/label&amp;gt;
  &amp;lt;textarea name=&amp;quot;message&amp;quot; rows=&amp;quot;16&amp;quot; cols=&amp;quot;28&amp;quot;&amp;gt;
  &amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;Envoyer&amp;quot;&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Nous devons maintenant gérer les données du formulaire lorsqu'il est validé.
Pour cela, nous allons créer un gestionnaire de type POST pour la même URL
&amp;quot;/contact&amp;quot; (celle que nous avons indiquée dans l'attribut action du formulaire
web) :&lt;/p&gt;
&lt;pre&gt;
post '/contact' do 
    require 'pony'
    Pony.mail(
      :from =&amp;gt; params[:name] + &amp;quot;&amp;lt;&amp;quot; + params[:email] + &amp;quot;&amp;gt;&amp;quot;,
      :to =&amp;gt; 'adresseperso@gmail.com',
      :subject =&amp;gt; &amp;quot;Vous avez un message de &amp;quot; + params[:name],
      :body =&amp;gt; params[:message],
      :port =&amp;gt; '587',
      :via =&amp;gt; :smtp,
      :via_options =&amp;gt; { 
        :address              =&amp;gt; 'smtp.gmail.com', 
        :port                 =&amp;gt; '587', 
        :enable_starttls_auto =&amp;gt; true, 
        :user_name            =&amp;gt; 'adresseperso', 
        :password             =&amp;gt; 'p@55w0rd', 
        :authentication       =&amp;gt; :plain, 
        :domain               =&amp;gt; 'localhost.localdomain'
      })
    redirect '/success' 
end
&lt;/pre&gt;
&lt;p&gt;Note : en local sur mon PC j'ai dû utiliser le port 25 au lieu du port
587 pour que l'&lt;a href=&quot;http://blog.pagesd.info/post/2009/04/21/SystemNetMail-et-smtpgmailcom&quot; hreflang=&quot;fr&quot;&gt;envoi de mail marche avec le SMTP de GMail&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Grosso modo, nous nous contentons de remplir les informations attendues par
Pony et d'envoyer le mail en utilisant notre compte GMail personnel (par
conséquent pensez à employer vos propres identifiants !). Si cela fonctionne
correctement, on doit recevoir un email avec le sujet &amp;quot;Vous avez un message de
Daz&amp;quot;. Al la fin du code, l'utilisateur est redirigé vers l'URL &amp;quot;/success&amp;quot; que
nous prenons en charge de la façon suivante :&lt;/p&gt;
&lt;pre&gt;
get('/success') {&amp;quot;Merci pour votre message. Nous vous contacterons bientôt.&amp;quot;}
&lt;/pre&gt;
&lt;p&gt;C'est clair qu'il faudrait développez un peu plus pour une vrai application,
mais vous voyez l'idée.&lt;/p&gt;
&lt;h3&gt;Heroku&lt;/h3&gt;
&lt;p&gt;Heroku vous permet d'utiliser l'option Sendgrid pour envoyer vos emails.
Vous pouvez vous inscrire pour bénéficier d'un compte gratuit (limité à 200
méls par jour) à l'aide de la commande suivante (à lancer via une console Git
bash) :&lt;/p&gt;
&lt;pre&gt;
$ heroku addons:add sendgrid:free
&lt;/pre&gt;
&lt;p&gt;Pour utiliser Sendgrid, vous devez modifier le paramétrage de Pony de la
façon suivante :&lt;/p&gt;
&lt;pre&gt;
post '/contact' do 
    require 'pony'
     Pony.mail(
      :from =&amp;gt; params[:name] + &amp;quot;&amp;lt;&amp;quot; + params[:email] + &amp;quot;&amp;gt;&amp;quot;,
      :to =&amp;gt; 'adresseperso@gmail.com',
      :subject =&amp;gt; &amp;quot;Vous avez un message de &amp;quot; + params[:name],
      :body =&amp;gt; params[:message],
      :port =&amp;gt; '587',
      :via =&amp;gt; :smtp,
      :via_options =&amp;gt; { 
        :address              =&amp;gt; 'smtp.sendgrid.net', 
        :port                 =&amp;gt; '587', 
        :enable_starttls_auto =&amp;gt; true, 
        :user_name            =&amp;gt; ENV['SENDGRID_USERNAME'], 
        :password             =&amp;gt; ENV['SENDGRID_PASSWORD'], 
        :authentication       =&amp;gt; :plain, 
        :domain               =&amp;gt; ENV['SENDGRID_DOMAIN']
      })
    redirect '/success' 
end
&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ENV['SENDGRID_USERNAME]&lt;/code&gt;, &lt;code&gt;ENV['SENDGRID_PASSWORD']&lt;/code&gt;
et &lt;code&gt;ENV['SENDGRID_DOMAIN']&lt;/code&gt; sont trois variables d'environnement de
Heroku qui sont définies automatiquement lorsque vous installez l'option
Sendgrid.&lt;/p&gt;
&lt;p&gt;En pratique, il serait plus judicieux de définir ces informations
séparément, en utilisant la commande &amp;quot;set&amp;quot; de Sinatra :&lt;/p&gt;
&lt;pre&gt;
set :email_username, ENV['SENDGRID_USERNAME] || 'adresseperso'
set :email_password, ENV['SENDGRID_PASSWORD'] || 'p@55w0rd'
set :email_address, 'daz@gmail.com'
set :email_service, ENV['EMAIL_SERVICE'] || 'gmail.com'
set :email_domain, ENV['SENDGRID_DOMAIN'] || 'localhost.localdomain'
&lt;/pre&gt;
&lt;p&gt;La variable d'environnement &lt;code&gt;ENV['EMAIL_SERVICE']&lt;/code&gt; n'étant pas
définie automatiquement, vous devez penser à l'initialiser vous-même avec la
commande suivante :&lt;/p&gt;
&lt;pre&gt;
$ heroku config:add EMAIL_SERVICE=sendgrid.net
&lt;/pre&gt;
&lt;p&gt;Et après cela, vous pouvez mettre à jour votre code pour envoyer un email en
utilisant Pony :&lt;/p&gt;
&lt;pre&gt;
post '/contact' do 
    require 'pony'
     Pony.mail(
      :from =&amp;gt; params[:name] + &amp;quot;&amp;lt;&amp;quot; + params[:email] + &amp;quot;&amp;gt;&amp;quot;,
      :to =&amp;gt; settings.email_address,
      :subject =&amp;gt; &amp;quot;Vous avez un message de &amp;quot; + params[:name],
      :body =&amp;gt; params[:message],
      :port =&amp;gt; '587',
      :via =&amp;gt; :smtp,
      :via_options =&amp;gt; { 
        :address              =&amp;gt; 'smtp.' + settings.email_service, 
        :port                 =&amp;gt; '587', 
        :enable_starttls_auto =&amp;gt; true, 
        :user_name            =&amp;gt; settings.email_username, 
        :password             =&amp;gt; settings.email_password, 
        :authentication       =&amp;gt; :plain, 
        :domain               =&amp;gt; settings.email_domain
      })
    redirect '/success' 
end
&lt;/pre&gt;
&lt;p&gt;Cette méthode vous permet d'utiliser votre formulaire de contact en local
(en utilisant votre adresse GMail perso) ou depuis Heroku (en passant par
Sendgrid).&lt;/p&gt;</description>
    
    
    
      </item>
    
  <item>
    <title>Changer de fenêtre sous VS 2010</title>
    <link>http://blog.pagesd.info/post/2011/04/28/changer-de-fenetre-sous-vs-2010</link>
    <guid isPermaLink="false">urn:md5:b9cb85da31a32f01657fec49c9703bc2</guid>
    <pubDate>Thu, 28 Apr 2011 09:30:00 +0200</pubDate>
    <dc:creator>Michel</dc:creator>
        <category>Code Snippets</category>    
    <description>    &lt;p&gt;Suite au visionnage de la présentation &lt;a href=&quot;http://channel9.msdn.com/events/MIX/MIX11/FRM03&quot; hreflang=&quot;us&quot;&gt;@:The Time is
Now&lt;/a&gt; de Phil Haack au MIX11, j'ai farfouillé dans Visual Studio 2010 et
fureté un peu sur l'internet afin de découvrir comment il s'y prenait pour
afficher une liste des fichiers ouverts.&lt;/p&gt;
&lt;p&gt;Et j'ai trouvé ! Il faut faire Ctrl+Tab :&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://blog.pagesd.info/public/img/CtrlTabVs2010.png&quot; alt=&quot;CtrlTabVs2010.png&quot; style=&quot;display:block; margin:0 auto;&quot; title=&quot;CtrlTabVs2010.png, avr. 2011&quot; /&gt;&lt;/p&gt;</description>
    
    
    
      </item>
    
  <item>
    <title>Jouer avec le feu</title>
    <link>http://blog.pagesd.info/post/2011/04/27/jouer-avec-le-feu</link>
    <guid isPermaLink="false">urn:md5:777725830aae6997865b49da2467c38b</guid>
    <pubDate>Wed, 27 Apr 2011 08:12:00 +0200</pubDate>
    <dc:creator>Michel</dc:creator>
        <category>Simplicité</category>    
    <description>    &lt;blockquote&gt;
&lt;p&gt;It’s rather disturbing how many problems are caused by ORMs, and how
developers will incessantly focus on data access rather than the business
problems they’re supposed to solve.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;http://wekeroad.com/post/4961628709/select-n-1-is-a-problem-we-made-for-ourselves&quot; hreflang=&quot;us&quot;&gt;Select N+1 Is a Problem We Made For Ourselves&lt;/a&gt;&lt;/p&gt;</description>
    
    
    
      </item>
    
  <item>
    <title>Gérer les virgules avec les Data Annotations</title>
    <link>http://blog.pagesd.info/post/2011/04/08/gerer-virgules-avec-data-annotations</link>
    <guid isPermaLink="false">urn:md5:a116cd5b7877d6a1f8522cbeab72f00f</guid>
    <pubDate>Fri, 08 Apr 2011 13:43:00 +0200</pubDate>
    <dc:creator>Michel</dc:creator>
        <category>.net</category><category>jQuery</category><category>MVC</category>    
    <description>    &lt;p&gt;Après avoir &amp;quot;réparé&amp;quot; mon &lt;a href=&quot;http://blog.pagesd.info/post/2011/03/30/vs-2010-sql-server-2008-express-windows-7-64-bits&quot; hreflang=&quot;us&quot;&gt;Visual Studio 2010&lt;/a&gt;, j'ai pu me remettre à la version 2 du
tutoriel &lt;a href=&quot;http://mvcmusicstore.codeplex.com/&quot; hreflang=&quot;us&quot;&gt;MVC Music
Store&lt;/a&gt; pour me confronter aux dernières (pour moi) technologies ASP.NET MVC
3 et entre autre Razor et les Data Annotations.&lt;/p&gt;
&lt;p&gt;Et justement, j'ai été un peu embarrassé par le fonctionnement de la
&lt;a href=&quot;http://www.asp.net/mvc/tutorials/mvc-music-store-part-6&quot; hreflang=&quot;us&quot;&gt;validation via les Data Annotations&lt;/a&gt; dès lors qu'on n'est pas des
yankees pure souche.&lt;/p&gt;
&lt;p&gt;Par exemple, si je veux passer le prix d'un album de 8,99 à 8,90 j'obtiens
l'erreur &amp;quot;The field Prix must be a number.&amp;quot; de la part du &lt;a href=&quot;http://bassistance.de/jquery-plugins/jquery-plugin-validation/&quot; hreflang=&quot;us&quot;&gt;plugin jQuery Validation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Et si j'essaie de contourner en saisissant 8.90 (avec un point au lieu de la
virgule), c'est l'erreur &amp;quot;The value '8.90' is not valid for Prix.&amp;quot; qui prend le
relai. Mais dans ce cas, cette erreur n'est pas renvoyée par jQuery Validation
mais par la méthode TryUpdateModel() dans le contrôleur : mon PC étant en
français, le .NET exige une virgule comme séparateur décimal.&lt;/p&gt;
&lt;p&gt;Zut ! Déjà les messages en anglais c'est pas tip-top. Mais que ça
m'affiche des valeurs numériques avec des virgules et que ça me gueule dessus
quand j'essaie de saisir c'est un peu pénible quand même.&lt;/p&gt;
&lt;p&gt;Jusqu'à présent, plutôt que de chercher à gérer le problème virgule, je me
contentais de bidouiller la section «globalization» dans le fichier web.config
pour que ASP.NET prenne lui aussi le &amp;quot;.&amp;quot; comme séparateur décimal :&lt;/p&gt;
&lt;pre&gt;
&amp;lt;configuration&amp;gt;
   &amp;lt;system.web&amp;gt;
      &amp;lt;globalization culture=&amp;quot;en-US&amp;quot; /&amp;gt;
   &amp;lt;/system.web&amp;gt;
&amp;lt;/configuration&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Mais cette fois-ci, je me suis dit que j'allai creuser un peu plus sinon ça
enlève pas mal d'intérêt aux Data Annotations.&lt;/p&gt;
&lt;h3&gt;Localiser les messages du plugin jQuery Validation&lt;/h3&gt;
&lt;p&gt;Déjà, quand on fait des recherches sur la localisation de jQuery Validation,
on se rend compte que c'est un problème général qui semble avoir été un peu
laissé de côté...&lt;/p&gt;
&lt;p&gt;Par contre, si on regarde dans le repository du plugin, il existe un
répertoire localization qui contient un fichier &lt;a href=&quot;https://github.com/jzaefferer/jquery-validation/blob/master/localization/messages_fr.js&quot; hreflang=&quot;fr&quot;&gt;messages_fr.js&lt;/a&gt; avec les messages d'erreur en français.&lt;/p&gt;
&lt;p&gt;Super ! Mais ça ne sert pas à grand chose parce que les messages
d'erreurs sont déjà initialisés directement par ASP.NET MVC :(&lt;/p&gt;
&lt;pre&gt;
&amp;lt;input data-val=&amp;quot;true&amp;quot; 
       data-val-number=&amp;quot;The field Prix must be a number.&amp;quot;
       data-val-range=&amp;quot;Le champ Prix doit &amp;amp;amp;#234;tre compris entre 0,01 et 100.&amp;quot;
       data-val-range-max=&amp;quot;100&amp;quot; 
       data-val-range-min=&amp;quot;0.01&amp;quot; 
       data-val-required=&amp;quot;Le champ Prix est requis.&amp;quot; 
       id=&amp;quot;Price&amp;quot; name=&amp;quot;Price&amp;quot; type=&amp;quot;text&amp;quot; value=&amp;quot;8,99&amp;quot; /&amp;gt;
&lt;/pre&gt;
&lt;p&gt;C'est malin ça de définir les messages d'erreur alors que le plugin jQuery
Validation les initialise déjà de son côté. Et c'est encore plus rigolo d'en
mettre certains en français et d'autres en anglais :)&lt;/p&gt;
&lt;h3&gt;Localiser &amp;quot;The field Xxxxx must be a number.&amp;quot;&lt;/h3&gt;
&lt;p&gt;Première méthode pour traduire ce message : supprimer l'attribut
&amp;quot;data-val-number&amp;quot; de toutes les zones de saisie et inclure le fichier
messages_fr.js (attention, l'ordre des scripts est important) :&lt;/p&gt;
&lt;pre&gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt;
    $(document).ready(function () {
        $(&amp;quot;:input[data-val-number]&amp;quot;).attr(&amp;quot;data-val-number&amp;quot;, &amp;quot;&amp;quot;);
    });
&amp;lt;/script&amp;gt;

&amp;lt;script src=&amp;quot;@Url.Content(&amp;quot;~/Scripts/jquery.validate.min.js&amp;quot;)&amp;quot; type=&amp;quot;text/javascript&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&amp;quot;@Url.Content(&amp;quot;~/Scripts/jquery.validate.unobtrusive.min.js&amp;quot;)&amp;quot; type=&amp;quot;text/javascript&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;

&amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt;

    /* ----- messages_fr.js ----- */
    /*
    * Translated default messages for the jQuery validation plugin.
    * Locale: FR
    */
    jQuery.extend(jQuery.validator.messages, {
        required: &amp;quot;Ce champ est requis.&amp;quot;,
        remote: &amp;quot;Veuillez remplir ce champ pour continuer.&amp;quot;,
        email: &amp;quot;Veuillez entrer une adresse email valide.&amp;quot;,
        url: &amp;quot;Veuillez entrer une URL valide.&amp;quot;,
        date: &amp;quot;Veuillez entrer une date valide.&amp;quot;,
        dateISO: &amp;quot;Veuillez entrer une date valide (ISO).&amp;quot;,
        number: &amp;quot;Veuillez entrer un nombre valide.&amp;quot;,
...
&lt;/pre&gt;
&lt;p&gt;Après ça, la saisie d'une valeur incorrecte dans la zone prix n'affiche plus
l'erreur &amp;quot;The field Prix must be a number.&amp;quot; mais &amp;quot;Veuillez entrer un nombre
valide.&amp;quot;.&lt;/p&gt;
&lt;p&gt;Deuxième méthode : si on veut continuer à indiquer le nom du champ qui
pose problème dans le message d'erreur, il faut sortir l'artillerie lourde et
utiliser les expressions régulières (l'ordre des scripts est toujours
important) :&lt;/p&gt;
&lt;pre&gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt;
    $(document).ready(function () {
        var reg_us = /The field (.+) must be a number\./gi;
        var msg_fr = &amp;quot;Le champ $1 doit être un nombre.&amp;quot;;
        $(&amp;quot;:input[data-val-number]&amp;quot;).each(function () {
            var message = $(this).attr(&amp;quot;data-val-number&amp;quot;);
            message = message.replace(reg_us, msg_fr);
            $(this).attr(&amp;quot;data-val-number&amp;quot;, message);
        });
    });
&amp;lt;/script&amp;gt;

&amp;lt;script src=&amp;quot;@Url.Content(&amp;quot;~/Scripts/jquery.validate.min.js&amp;quot;)&amp;quot; type=&amp;quot;text/javascript&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&amp;quot;@Url.Content(&amp;quot;~/Scripts/jquery.validate.unobtrusive.min.js&amp;quot;)&amp;quot; type=&amp;quot;text/javascript&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;

&amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt;

    /* ----- messages_fr.js ----- */
    /*
    * Translated default messages for the jQuery validation plugin.
    * Locale: FR
    */
    jQuery.extend(jQuery.validator.messages, {
        required: &amp;quot;Ce champ est requis.&amp;quot;,
...
&lt;/pre&gt;
&lt;p&gt;Dans ce cas, la saisie d'une valeur incorrecte dans le prix affiche le
message &amp;quot;Le champ Prix doit être un nombre.&amp;quot;.&lt;/p&gt;
&lt;h3&gt;Faire accepter les nombres à virgule à jQuery Validation&lt;/h3&gt;
&lt;p&gt;C'est bien beau de parler à l'utilisateur en français, mais c'est quand même
plus important de lui permettre de pouvoir saisir le prix qu'il veut sans avoir
à abandonner les valeurs décimales.&lt;/p&gt;
&lt;p&gt;Et là, le plugin jQuery Validation a tout prévu puisque le répertoire
localisation contient également des fichiers methods_de.js, methods_nl.js et
methods_pt.js. Mais malheureusement pour moi, pas de methods_fr.js en vue
:(&lt;/p&gt;
&lt;p&gt;En y regardant de plus près, le fichier &lt;a href=&quot;https://github.com/jzaefferer/jquery-validation/blob/master/localization/methods_de.js&quot; hreflang=&quot;us&quot;&gt;methods_de.js&lt;/a&gt; devrait faire l'affaire. Ce qui se confirme sur
le forum &lt;a href=&quot;http://www.developpez.net/forums/d1049394/dotnet/developpement-web/asp-net-mvc/asp-net-mvc-3-probleme-decimal-jquery/#post5832403&quot; hreflang=&quot;fr&quot;&gt;developpez.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Ni une, ni deux, il suffit de compléter les scripts de la façon suivante
(l'ordre des scripts est important) :&lt;/p&gt;
&lt;pre&gt;
&amp;lt;script src=&amp;quot;@Url.Content(&amp;quot;~/Scripts/jquery.validate.min.js&amp;quot;)&amp;quot; type=&amp;quot;text/javascript&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&amp;quot;@Url.Content(&amp;quot;~/Scripts/jquery.validate.unobtrusive.min.js&amp;quot;)&amp;quot; type=&amp;quot;text/javascript&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;

&amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt;
jQuery.extend(jQuery.validator.methods, {
    date: function(value, element) {
        return this.optional(element) || /^\d\d?\.\d\d?\.\d\d\d?\d?$/.test(value);
    },
    number: function(value, element) {
        return this.optional(element) || /^-?(?:\d+|\d{1,3}(?:\.\d{3})+)(?:,\d+)?$/.test(value);
    }
});
&amp;lt;/script&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Et maintenant si je change mon prix en 8,90, je n'ai plus le message &amp;quot;Le
champ Prix doit être un nombre.&amp;quot;. Non. Maintenant ça me dit que &amp;quot;Le champ Prix
doit être compris entre 0,01 et 100.&amp;quot;.&lt;/p&gt;
&lt;p&gt;C'est des coriaces !&lt;/p&gt;
&lt;h3&gt;Gérer les virgules dans jQuery Validation&lt;/h3&gt;
&lt;p&gt;Si c'est ça, je regarde dans le source de &lt;a href=&quot;https://github.com/jzaefferer/jquery-validation/blob/master/jquery.validate.js&quot; hreflang=&quot;us&quot;&gt;jquery.validate.js&lt;/a&gt;. Ah ben bien sûr, y'a un problème avec le
&amp;quot;range&amp;quot; :&lt;/p&gt;
&lt;pre&gt;
range: function( value, element, param ) {
    return this.optional(element) || ( value &amp;gt;= param[0] &amp;amp;&amp;amp; value &amp;lt;= param[1] );
},
&lt;/pre&gt;
&lt;p&gt;Il compare &lt;code&gt;value&lt;/code&gt; (le 8,90 que j'ai saisi) avec
&lt;code&gt;param[0]&lt;/code&gt; (la valeur minimum de 0.01) et &lt;code&gt;param[1]&lt;/code&gt; (la
valeur maximum de 100). Personne lui a jamais dit qu'on ne peut pas comparer
les points et les virgules ?&lt;/p&gt;
&lt;p&gt;Et le pire, c'est qu'il fait pareil avec les fonctions &amp;quot;min&amp;quot; et max&amp;quot; le
bougre ! Ca va pas se passer comme ça :&lt;/p&gt;
&lt;pre&gt;
/* ----- methods_fr.js ----- */
/*
* Localized default methods for the jQuery validation plugin.
* Locale: FR
*/
jQuery.extend(jQuery.validator.methods, {
    min: function (value, element, param) {
        return this.optional(element) || replaceComma(value) &amp;gt;= replaceComma(param);
    },
    max: function (value, element, param) {
        return this.optional(element) || replaceComma(value) &amp;lt;= replaceComma(param);
    },
    range: function (value, element, param) {
        value = replaceComma(value);
        return this.optional(element) || (value &amp;gt;= replaceComma(param[0]) &amp;amp;&amp;amp; value &amp;lt;= replaceComma(param[1]));
    },
    date: function (value, element) {
        return this.optional(element) || /^\d\d?\.\d\d?\.\d\d\d?\d?$/.test(value);
    },
    number: function (value, element) {
        return this.optional(element) || /^-?(?:\d+|\d{1,3}(?:\.\d{3})+)(?:,\d+)?$/.test(value);
    }
});

function replaceComma(value) {
    // Quick &amp;amp; Dirty replace &amp;quot;,&amp;quot; by &amp;quot;.&amp;quot; as decimal separators
    return value.replace(&amp;quot;,&amp;quot;, &amp;quot;.&amp;quot;);
}
&lt;/pre&gt;
&lt;p&gt;Et après ça, 8,90 passe enfin alors que 100,01 est bien refusé !&lt;/p&gt;
&lt;p&gt;Ouf ! Ca devrait faire l'affaire jusqu'à la sortie de &lt;a href=&quot;https://github.com/jzaefferer/jquery-validation/issues#issue/55&quot; hreflang=&quot;us&quot;&gt;jQuery Validation 2.0&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mise à jour&lt;/strong&gt; : si j'avais cherché mieux, j'aurais pu
trouver le billet &lt;a href=&quot;http://blogs.msdn.com/b/rickandy/archive/2011/02/17/using-mvc-3-with-non-english-locales.aspx&quot; hreflang=&quot;us&quot;&gt;Using MVC 3 with non-English Locales&lt;/a&gt; de Rick Anderson, le
co-auteur du tutoriel &lt;a href=&quot;http://www.asp.net/mvc/tutorials/getting-started-with-mvc3-part1-cs&quot; hreflang=&quot;us&quot;&gt;Getting Started With MVC3&lt;/a&gt;.&lt;/p&gt;</description>
    
    
    
      </item>
    
  <item>
    <title>VS 2010, SQL Server 2008 Express et Windows 7 64 bits</title>
    <link>http://blog.pagesd.info/post/2011/03/30/vs-2010-sql-server-2008-express-windows-7-64-bits</link>
    <guid isPermaLink="false">urn:md5:1fdfc37c83389e3ded3bc3f617e54c74</guid>
    <pubDate>Wed, 30 Mar 2011 12:45:00 +0200</pubDate>
    <dc:creator>Michel</dc:creator>
        <category>boulot</category>    
    <description>    &lt;p&gt;J'ai rechuté. Après mes problèmes d'incompatibilités entre &lt;a href=&quot;http://blog.pagesd.info/post/2009/11/25/VS2008-SQL-Server-2008-Express-Windows7-64-bits&quot; hreflang=&quot;fr&quot;&gt;VS 2008, SQL Server et Windows 7 64 bits&lt;/a&gt;, j'ai encore trouvé
le moyen d'avoir un autre problème tout aussi bizarroïde.&lt;/p&gt;
&lt;p&gt;Suite à je ne sais trop quelles mises à jour sur mon PC (peut-être .NET 4 ou
MVC3 ?) je me suis retrouvé dans l'impossibilité d'utiliser SQL Server 2008
avec Visual Studio 2010, notamment en essayant de refaire le tutoriel &lt;a href=&quot;http://mvcmusicstore.codeplex.com/&quot; hreflang=&quot;us&quot;&gt;MVC Music Store&lt;/a&gt; V2.
Alors que mes collègues n'avaient pas ce problème :(.&lt;/p&gt;
&lt;p&gt;Dès que j'essayais d'accéder à une base de données, je tombais
immanquablement sur le message d'erreur suivant :&lt;/p&gt;
&lt;pre&gt;
Le fournisseur de données .Net Framework demandé est introuvable. Il n'est peut-être pas installé.
&lt;/pre&gt;
&lt;p&gt;Malgré des tas de recherches sur Google (s*perie de messages en français),
impossible de trouver quoique ce soit de valable à ce sujet. J'en étais presque
arrivé au point de vouloir tout ré-installer (parce que quand on n'installe pas
dans le bon ordre c'est déjà galère, alors j'ose même pas imaginer ce que ça
donnerait de dés-installer / ré-installer morceau par morceau...).&lt;/p&gt;
&lt;p&gt;Jusqu'au moment où j'ai découvert fortuitement que le problème n'était pas
lié à SQL Server, mais plutôt à Entity Framework. En effet, en transformant une
solution VS 2008 sous VS 2010 je me suis rendu compte qu'elle fonctionnait
correctement alors qu'elle utilisait bel et bien une base de données SQL
Server ! La seule différence, c'est qu'elle utilisait LINQ to SQL et pas
EF.&lt;/p&gt;
&lt;p&gt;A partir de ça j'ai réussi à trouver comment régler le problème en réparant
le client .NET Framework 4, ce qui a eu pour effet de re-créer le fichier
machine.config : &lt;a href=&quot;http://stackoverflow.com/questions/3503957/visual-studio-2010-add-connection-dialogue-not-populating-databases/4138842#4138842&quot; hreflang=&quot;us&quot;&gt;Visual Studio 2010 Add Connection dialogue not populating
databases&lt;/a&gt; :&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I managed to solve the problem by moving the machine.config file from
&amp;quot;C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config&amp;quot; and
&amp;quot;C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config&amp;quot; to another location (as
a backup) and then ran a repair on the .NET Framework 4 Client Profile. This
rebuilt the machine.config file and now everything works fine.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Merci StackOverflow et Zoran.&lt;/p&gt;</description>
    
    
    
      </item>
    
  <item>
    <title>Introduction à Haml</title>
    <link>http://blog.pagesd.info/post/2011/03/09/introduction-a-haml</link>
    <guid isPermaLink="false">urn:md5:42237a9555482ed9bcb1d54e28a0d815</guid>
    <pubDate>Wed, 09 Mar 2011 18:53:00 +0100</pubDate>
    <dc:creator>Michel</dc:creator>
        <category>Haml</category><category>Ruby</category>    
    <description>    &lt;p&gt;J'aime bien les vidéos de Screencasts.org, surtout leur côté 5/10 minutes
vite fait pour aborder (ou réviser) un sujet. Après l'&lt;a href=&quot;http://blog.pagesd.info/post/2011/03/08/introduction-a-sinatra&quot; hreflang=&quot;fr&quot;&gt;introduction à Sinatra&lt;/a&gt;, j'ai donc décidé de suivre l'&lt;a href=&quot;http://screencasts.org/episodes/introduction-to-haml&quot; hreflang=&quot;us&quot;&gt;introduction à Haml&lt;/a&gt;. &lt;a href=&quot;http://haml-lang.com/&quot; hreflang=&quot;us&quot;&gt;Haml&lt;/a&gt; (qui se prononce AMeul, comme dans meule) est un langage de
template très concis destiné à remplacer HTML dans les applications Rails ou
Sinatra.&lt;/p&gt;
&lt;p&gt;Cette fois-ci, la vidéo est accompagnée de sa retranscription complète ce
qui rend son visionnage encore plus facile.&lt;/p&gt;
&lt;p&gt;Après 10 secondes de publicité, on enchaine sur un rappel de la dure
condition des développeurs en quête de beauté et de simplicité dans un monde
d'IE6, SOAP et autre Dreamweaver.&lt;/p&gt;
&lt;p&gt;On rentre ensuite dans le vif du sujet en démarrant avec un petit morceau de
code HTML (beurk beurk beurk) tel qu'on en voit souvent :&lt;/p&gt;
&lt;pre&gt;
&amp;lt;div id=&amp;quot;profile&amp;quot;&amp;gt;
  &amp;lt;div class=&amp;quot;left column&amp;quot;&amp;gt;
    &amp;lt;div id=&amp;quot;date&amp;quot;&amp;gt; 11 November 2010 &amp;lt;/div&amp;gt;
    &amp;lt;div id=&amp;quot;address&amp;quot;&amp;gt; 1 Infinite Loop &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;right column&amp;quot;&amp;gt;
    &amp;lt;div id=&amp;quot;email&amp;quot;&amp;gt; steve@apple.com &amp;lt;/div&amp;gt;
    &amp;lt;div id=&amp;quot;bio&amp;quot;&amp;gt; Makes magical tablets &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Puis, à partir de ce truc, on nous retrace par petites touches successives
la genèse de Haml (miam miam miam) :&lt;/p&gt;
&lt;pre&gt;
#profile
  .left.column
    #date 11 November 2010
    #address 1 Infinite Loop
  .right.column
    #email steve@apple.com
    #bio Makes magical tablets
&lt;/pre&gt;
&lt;p&gt;Pour bien enfoncer le clou, on fait aussi le parallèle entre le template Erb
correspondant (ouuh !) :&lt;/p&gt;
&lt;pre&gt;
&amp;lt;div id=&amp;quot;profile&amp;quot;&amp;gt;
  &amp;lt;div class=&amp;quot;left column&amp;quot;&amp;gt;
    &amp;lt;div id=&amp;quot;date&amp;quot;&amp;gt;&amp;lt;%= print_date %&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div id=&amp;quot;address&amp;quot;&amp;gt;&amp;lt;%= user.address %&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;right column&amp;quot;&amp;gt;
    &amp;lt;div id=&amp;quot;email&amp;quot;&amp;gt;&amp;lt;%= user.email %&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div id=&amp;quot;bio&amp;quot;&amp;gt;&amp;lt;%= user.bio %&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Et l'équivalent sous Haml (ouah !) :&lt;/p&gt;
&lt;pre&gt;
#profile
  .left.column
    #date= print_date
    #address= user.address
  .right.column
    #email= user.email
    #bio= user.bio
&lt;/pre&gt;
&lt;p&gt;Viennent ensuite quelques explications sur la syntaxe :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;!!! pour déclarer du doctype&lt;/li&gt;
&lt;li&gt;%element pour définir un élément (%html pour la balise &amp;lt;html ...&amp;gt;, %p
pour la balise &amp;lt;p&amp;gt;...)&lt;/li&gt;
&lt;li&gt;%element#id pour définir un identifiant&lt;/li&gt;
&lt;li&gt;%element.class pour définir une classe&lt;/li&gt;
&lt;li&gt;possibilité d'omettre %div qui est la balise par défaut&lt;/li&gt;
&lt;li&gt;ajouter des attributs sous la forme (attribut=&amp;quot;valeur&amp;quot;)&lt;/li&gt;
&lt;li&gt;importance de l'indentation pour imbriquer les balises&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Le tutoriel se termine par l'installation d'Haml (&lt;code&gt;gem install
haml&lt;/code&gt;), un exemple d'utilisation en ligne de commande et un lien pour
&lt;a href=&quot;http://haml-lang.com/try.html&quot; hreflang=&quot;us&quot;&gt;tester Haml en
ligne&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pour l'instant, je suis dubitatif et je me demande si c'est une vrai bonne
idée de masquer autant que ça HTML ?&lt;/p&gt;
&lt;p&gt;Juste pour référence, voilà ce que l'exemple de code donnerait avec &lt;a href=&quot;http://haacked.com/archive/2011/01/06/razor-syntax-quick-reference.aspx&quot; hreflang=&quot;us&quot;&gt;Razor&lt;/a&gt; où on reste plus calqué sur le code HTML :&lt;/p&gt;
&lt;pre&gt;
&amp;lt;div id=&amp;quot;profile&amp;quot;&amp;gt;
  &amp;lt;div class=&amp;quot;left column&amp;quot;&amp;gt;
    &amp;lt;div id=&amp;quot;date&amp;quot;&amp;gt;@Me.print_date()&amp;lt;/div&amp;gt;
    &amp;lt;div id=&amp;quot;address&amp;quot;&amp;gt;@user.address&amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;right column&amp;quot;&amp;gt;
    &amp;lt;div id=&amp;quot;email&amp;quot;&amp;gt;@user.email&amp;lt;/div&amp;gt;
    &amp;lt;div id=&amp;quot;bio&amp;quot;&amp;gt;@user.bio&amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Malgré tout (pour s'entrainer en attendant de suivre l'&lt;a href=&quot;http://screencasts.org/episodes/introduction-to-sass&quot; hreflang=&quot;us&quot;&gt;introduction à Sass&lt;/a&gt;) le site &lt;a href=&quot;http://html2haml.heroku.com/&quot; hreflang=&quot;us&quot;&gt;Html2Haml&lt;/a&gt; permet de convertir du code HTML en Haml et un
&lt;a href=&quot;http://thomas-brian.developpez.com/articles/haml/&quot; hreflang=&quot;fr&quot;&gt;tutoriel en français sur Haml&lt;/a&gt;.&lt;/p&gt;</description>
    
    
    
      </item>
    
</channel>
</rss>
