Loupe

Transformer ses fichiers de configuration à la build

Vous qui faites du web depuis un moment, vous êtes probablement déjà au courant que les transformations des fichiers de configuration XML se font uniquement lors de la publication de notre application. C’est génial, mais en tant que développeur, on aimerait bien pouvoir débugger dans une configuration particulière. C’est possible, de manière saine et sans avoir à copier coller à tout va les settings, en faisant la transformation soit même juste avant que l’application build ! Voyons rapidement comment procéder, étapes par étapes :

Préparer les fichiers nécessaires

Personnellement, j’aime bien découper les fichiers et sortir les AppSettings & ConnectionStrings dans des fichiers séparés. Nous allons prendre l’exemple avec les AppSettings, que nous allons référencer dans le Web.config de la sorte :

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <appSettings file="./.config/AppSettings.config" />
  ...

 

L’objectif va être de générer le contenu de ce fichier, pour cela nous aurons besoin d’un fichier qui servira de “base”  et que l’on appellera AppSettings.Base.config. C’est ce fichier qui va être dérivé en autant de configuration que l’on a dans notre solution/projet. De base, ayant Debug & Release, nous aurons la configuration suivante :

Sans titre

 

Nous allons donc créer dans un dossier à la racine nommé “.config”. On y rajoute le fichier AppSettings.config, vide, qui sera en Build Action : Content avec Copy if newer, il est important que ce fichier soit référencé dans la solution pour qu’à la publication, le fichier soit envoyé sur le serveur. Ensuite, nous créons le fichier AppSettings.Base.config avec le contenu suivant :

<?xml version="1.0"?>
<!-- AppSettings.config IN THIS PROJECT SHOULD ONLY BE MODIFIED BY -->
<!-- AppSettings.Base.config -->
<!-- AppSettings.(Configuration).config -->
<!-- CHANGES MADE DIRECTLY TO THE AppSettings.config WILL BE OVERWRITTEN by build process -->
<appSettings>
  <add key="Environment" value="Base Env"/>
</appSettings>

 

J’ai tendance à rajouter les commentaires ci-dessus en entête de chaque fichier pour clarifier les choses et rappeler aux développeurs (et donc moi même !) de ne pas toucher à l’AppSettings.config qui sera généré de toute façon !

Pour créer les fichiers dérivés, vous pouvez installer SlowCheetah, qui vous rajoutera, entre autre, un menu contextuelle vous permettant de créer les fichiers automatiquement :

https://www.nuget.org/packages/SlowCheetah/

 

Voila un exemple de transformation pour le fichier AppSettings.Base.Release.config :

 

<?xml version="1.0"?>
<!-- AppSettings.config IN THIS PROJECT SHOULD ONLY BE MODIFIED BY -->
<!-- AppSettings.Base.config -->
<!-- AppSettings.(Configuration).config -->
<!-- CHANGES MADE DIRECTLY TO THE AppSettings.config WILL BE OVERWRITTEN by build process -->
<appSettings xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <add key="Environment" value="Release Env" xdt:Locator="Match(key)" xdt:Transform="Replace"/>
</appSettings>

 

Ici, on spécifie avec xdt:Locator=”Match(key)” que si la clé du setting match avec un setting du fichier source, on va appliquer la transformation spécifiée dans l’attribut xdt:Transform, qui est Replace. La nouvelle valeur du setting avec la clé Environment sera donc “Release Env”.

Pour en savoir plus sur les possibilités sur la transformation avec le namespace xdt, je vous invite à consulter la documentation suivante :

https://msdn.microsoft.com/en-us/library/dd465326(v=vs.110).aspx

Exécuter la transformation avant la build

On va aller dans le .csproj (on peut unload le projet dans Visual Studio puis ensuite éditer le fichier .csproj) et rajouter à la fin, juste avant la fermeture de la balise </Project> le code suivant :

  <!-- BUILD DEPENDS ON-->
  <PropertyGroup>
    <BuildDependsOn>
      AppSettingsConfigExistsTransform;
      AppSettingsConfigNotExistsTransform;
      $(BuildDependsOn);
    </BuildDependsOn>
  </PropertyGroup>
  
  <!-- APP SETTINGS -->
  <Target Name="AppSettingsConfigExistsTransform" Condition="Exists('.config/AppSettings.Base.$(Configuration).config')">
    <!-- Exist so we transform the file-->
    <Message Importance="high" 
             Text="---- TRANSFORM-AppSettings : AppSettings.Base.config + AppSettings.$(Configuration).config -&gt; AppSettings.config" />
    <TransformXml  Source=".config/AppSettings.Base.config" 
                   Transform=".config/AppSettings.Base.$(Configuration).config" 
                   Destination=".config/AppSettings.config" />
  </Target>
  
  <Target Name="AppSettingsConfigNotExistsTransform" Condition="!Exists('.config/AppSettings.Base.$(Configuration).config')">
    <!-- Does not exist so we only copy base-->
    <Message Importance="high" 
             Text="---- TRANSFORM-ConnectionStrings : AppSettings.Base.config -&gt; AppSettings.config" />
    <Copy SourceFiles=".config/AppSettings.Base.config" 
          DestinationFiles=".config/AppSettings.config" />
  </Target>

 

Le premier bloc <PropertyGroup> va signifier que l’on veut exécuter AppSettingsConfigExistsTransform et AppSettingsConfigNotExistsTransform avant la build : $(BuildDependsOn).

Le deuxième bloc AppSettingsConfigExistsTransform est exécuté seulement si un fichier avec le format AppSettings.Base.$(Configuration).config existe, comme par exemple AppSettings.Base.Release.config, existe. On va alors générer le contenu du fichier AppSettings.config à ce moment-là. Toutefois, il se peut que le fichier de configuration n’existe pas, dans quel cas nous copions tout simplement le contenu du fichier de base vers la destination.

 

Et voilà !

Il suffit de lancer son application et examiner le contenu de l’appSetting “Environment” :

System.Configuration.ConfigurationManager.AppSettings["Environment"] 

 

On verra bien qu’à l’exécution, le contenu est remplacé par les valeurs associées à la configuration courante, grâce à la transformation ! Vous avez maintenant une gestion des settings en fonction de vos configuration qui est fonctionnelle autant en debug qu’au publish. Il suffit juste d’exclure le fichier généré du source control (tfignore / gitignore), car le contenu risque de changer souvent si vous régénérez la solution avec une configuration différente !

Au fait, vous pouvez très bien faire cela sur vos ConnectionStrings, ou bien sur le Web.config, mais aussi sur les app.config d’applications consoles par exemple ! Cela veut dire que vous pouvez vous permettre d’avoir le même mécanismes sur vos Websites, API et Azure WebJobs par exemple…!

ATTENTION tout de même, il existe un bug au niveau des Azure WebJobs, où les settings du portail Azure ne sont pas pris en compte si les AppSettings sont référencés dans un fichier externe au app.config, on doit donc se contenter de mettre nos AppSettings dans le app.config directement !

J’espère que cela vous sera utile pour vos projets !

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus