ASP.net MVC + Less = Bonne ou mauvaise idée?
ASP .net est, c’est sympa. Less l’est aussi. Mais est-ce que les deux font vraiment la paire? Simples? Efficaces? QUID de la productivité avec Visual Studio?
Pour ceux qui ne connaitraient pas Less : Il s’agit d’une solution de pre-processing de CSS. Pour faire simple, il s’agit d’un langage qui permet de produire des CSS. Il permet d’utiliser des variables, des méthodes, pour définir des couleurs, des unités… etc… Changer une couleur utiliser par X classes devient donc tout de suite plus facile et rapide (fini le Ctrl+H).
Pour ne savoir davantage d’information : http://lesscss.org/
Utiliser Less avec n’est pas forcément très compliqué. De base, deux options s’offrent à nous :
- Compiler les fichiers Less et inclure les CSS produites dans son projet.
- Inclure les fichiers Less dans son projet et inclure une librairie qui permette d’interpréter le code Less et d’obtenir des CSS.
Mon avis sur la première option
La première option implique une opération de build du Less systématique avant le build de l’application web. Ceci via un outil tiers, un package npm, ou autre… Si on veut limiter le nombre d’outils à installer et à maintenir sur le poste de développement et/ou sur les serveurs de build, cette option n’est pas forcément la plus adaptée.
Mon avis sur la seconde option
Outre le fais de ne pas nécessiter d’outils tiers, la seconde option permet :
- De disposer d’une librairie disponible via Nuget. (le déploiement et la mise à jour sont donc simples)
- D’inclure ces fichiers Less dans un Bundle, en Debug et en release. (le Debug et donc simple et possible en cours d’exécution, et en production on peut profiter du regroupement de fichier et de la compression)
Dit comme cela, la solution semble idyllique. Dans les faits, il y a cependant quelques précautions à prendre :
- Le package Nuget DotLess effectue quelques modifications sur le fichier web.config qu’il convient de maitriser pour ne pas avoir de problèmes lors de la mise en production.
- Le bundle de plusieurs fichiers Less interdépendants implique l’ajout d’un peu de code (l’usage d’un seul fichier Less ne demande aucun code supplémentaire).
Il faut donc savoir modifier son fichier de configuration et savoir mettre ces fichiers Less en Bundle.
Mon choix personnel
Moi qui aime des serveurs de build et des PC clean, je n’adhère pas vraiment à la première solution.
Le fait de pouvoir modifier mes fichiers Less alors que mon site tourne en Debug est pour moi un must. Je préfère donc utiliser la seconde option.
Mise en place de DotLess
Pour commencer, il suffit d’installer le package nuget dotless.
Install-Package dotless
Ensuite, on modifie son fichier de configuration web.config.
Dans le cas où l’application finira sur un serveur web avec un Pool ASP .net intégré, il faudra supprimer le httpHandlers lié aux fichiers *.less
<system.web>
<httpHandlers>
<add path="*.less" verb="GET" type="dotless.Core.LessCssHttpHandler, dotless.Core" />
</httpHandlers>
</system.web>
Les fichiers Less étant utilisés au travers de bundles, la section spécifique peut être supprimée.
<configSections>
<section name="dotless" type="dotless.Core.configuration.DotlessConfigurationSectionHandler, dotless.Core" />
</configSections>
<dotless minifyCss="false" cache="true" web="false" strictMath="false" />
Il est impératif de conserver le Hander dans la section serveur. ex :
<system.webServer>
<handlers>
<add name="dotless" path="*.less" verb="GET" type="dotless.Core.LessCssHttpHandler,dotless.Core" resourceType="File" preCondition="" />
<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
<remove name="OPTIONSVerbHandler" />
<remove name="TRACEVerbHandler" />
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>
</system.webServer>
Utilisation de Bundles
Côté Bundle, il faut prendre quelques habitudes:
- Déposer tous les fichiers Less d’un bundle dans un même répertoire.
- Créer un fichier Less qui référencera les autres via la directive @import
- Inclure ce fichier Less dans un Bundle.
- Coder une class qui se chargera de transformer le Less en CSS (interface à implémenter IBundleTransform)
- Coder une classe qui aidera le Bundle à trouver le répertoire qui contient tous les fichiers Less (interface à implémenter : IFileReader).
Les transformations n’intervenant sur les Bundles que lorsque l’application est en release, il convient de tester la solution et Debug et en Release… pour éviter les mauvaises surprimes.
Étape 1
Pour mon exemple, j’ai choisi d’utiliser les fichiers Less de Bootstrap. Je les ai donc déposés dans un dossier Bootstrap dans le dossier Content de mon application web.
Étape 2
Je créé un fichier _site.less qui importe les éléments de Bootstrap dont j’ai l’utilité:
// Core variables and mixins
@import "_variables.less";
@import "mixins.less";
// Reset and dependencies
@import "normalize.less";
// Core CSS
@import "scaffolding.less";
@import "type.less";
@import "code.less";
@import "grid.less";
@import "tables.less";
@import "forms.less";
@import "buttons.less";
Étape 3
Je créé mon bundle "~/Content/Layout". Au passage, je peux y inclure quelques CSS supplémentaires:
public static class BundleConfig
{
public static void RegisterBundles(BundleCollection bundles)
{
#region Less & CSS Layout
// Style produit via Less
Bundle lessBundle = new Bundle("~/Content/Layout").Include(
"~/Content/bootstrap/_site.less",
"~/Content/bootstrap/bootstrap-accessibility.css",
"~/Content/font-awesome.css",
"~/Content/Accessibility.css"
);
lessBundle.Transforms.Add(new BootstrapLessTransform());
lessBundle.Transforms.Add(new CssMinify());
bundles.Add(lessBundle);
#endregion
}
}
Étape 4
Je code mon BootstrapLessTransform qui utilise DotLess pour transformer mes fichiers Less en CSS. La BootstrapSource est présentée dans l’étape suivante.
public sealed class BootstrapLessTransform : IBundleTransform
{
public void Process(BundleContext context, BundleResponse response)
{
DotlessConfiguration config = new DotlessConfiguration();
//config.ImportAllFilesAsLess = true;
config.LessSource = typeof(BootstrapSource);
response.Content = dotless.Core.Less.Parse(response.Content, config);
response.ContentType = "text/css";
}
}
Étape 6
Je code la BootstrapSource qui permet de trouver les fichiers Less présents dans le dossier “~/Content/bootstrap/”. Pour les cas où je dois avoir plusieurs sources dans un même projet, j’ai prévu une classe de base VirtualFileReader qui m’évite de dupliquer trop de code.
public sealed class BootstrapSource : VirtualFileReader
{
public BootstrapSource() : base("~/Content/bootstrap/")
{
}
}
public abstract class VirtualFileReader : IFileReader
{
private readonly String _root;
public VirtualFileReader(String root)
{
_root = root;
}
public bool UseCacheDependencies
{
get { return false; }
}
public bool DoesFileExist(string fileName)
{
return File.Exists(GetFullPath(fileName));
}
public byte[] GetBinaryFileContents(string fileName)
{
return File.ReadAllBytes(GetFullPath(fileName));
}
public string GetFileContents(string fileName)
{
return File.ReadAllText(GetFullPath(fileName));
}
private String GetFullPath(String path)
{
return HostingEnvironment.MapPath(_root + path);
}
}
Conclusion
Voilà, une solution simple, efficace et qui ne soufre au final d’aucun inconvénient, à partir du moment où l’on suit les conseils présentés ici.