Connection Strings für Test-/Win- und Consoleprojekte transformieren

20. Mai 2011

Mit VS2010 hat Microsoft eine Transformations-Syntax eingeführt, mit deren Hilfe man Web.config Dateien durch den Build-Prozess manipulieren kann. Einzelne Konfigurationswerte werden, abhängig von der Build-Configuration, modifiziert oder hinzugefügt (ge-patched). Somit lassen sich mit geringem Aufwand unterschiedliche Konfigurationen für unterschiedliche Zielplattformen erzeugen.

Im nachfolgenden wird gezeigt, wie diese Funktionalität auch für beliebige Projekttypen genutzt werden kann und wie ein TFS Build davon profitiert.

Der Einsatz in Webprojekten

Einer der beliebtesten Anwendungsfälle für die Transformations-Syntax ist die Manipulation von ConnectionStrings in Abhängigkeit des Build-Targets. Kurzum: Debug-Build und Release-Build nutzen unterschiedliche Datenbanken.

Ausgangspunkt für nachfolgendes Beispiel ist eine ganz normale Web.config, die einen ConnectionString auf die lokale Datenbank “MyDb” enthält.

Web.config:

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

 

  <connectionStrings>

    <add name="MyDbConnection"

         connectionString="Data Source=(local);Initial Catalog=MyDb;"

         providerName="System.Data.SqlClient"/>

  </connectionStrings>

  <system.diagnostics>

    ...

  </system.diagnostics>

  ...

</configuration>

Für das Release-Build soll nun ein anderer ConnectionString verwendet werden. Dieser wird in einer separaten Datei Web.Release.config konfiguriert. Der Dateinamen folgt dem Namensschema: Web.<BuildConfiguration>.config.

Diese Datei enthält Anweisungen in der XML-Document-Transform Syntax (XDT) [1], die den Patch an der web.config beschreibt. Sobald der Benutzer die Release-Configuration auswählt und den Build startet, werden die Änderungen angewendet. Im Beispiel wird für ein Release-Build der ConnectionString der Web.config ersetzt. Datenbankname und Servername sind gegenüber der originalen web.config geändert. Alle unveränderten Elemente der Web.config wie beispielsweise <system.diagnostic> werden nicht wiederholt.

XDT-Anweisungen in der Web.Release.config, zur Änderung ConnectionString

<?xml version="1.0"?>

<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">

  <connectionStrings>

    <add name="MyDbConnection"

         connectionString="Data Source=ProdServer;Initial Catalog=MyProdDb;" 

         providerName="System.Data.SqlClient"

         xdt:Transform="SetAttributes" 

         xdt:Locator="Match(name)"/>

  </connectionStrings>

</configuration>

Das XDT Attribut xdt:Locator=”Match(name)” weißt die Transformation an, das XML-Element mit gleichem “name” in der web.config zu suchen. Die Transformationanweisung xdt:Transform=”SetAttributes” überschreibt die vorhandene Attribute connectiongString, providername. Alle anderen XML-Elemente der originalen web.config bleiben von der Transformation unverändert.

Die Ausführung erfolgt während des Builds. D.h. die transformierte Datei wird in das Output-Dir des Projekts geschrieben.

Ergebnisdatei web.config im Output-Dir:

<configuration>
<?xml version="1.0" encoding="utf-8" ?>

 

  <connectionStrings>

    <add name="MyDbConnection"

         connectionString="Data Source=ProdServer;Initial Catalog=MyProdDb;"

         providerName="System.Data.SqlClient"/>

  </connectionStrings>

  <system.diagnostics>

    ...

  </system.diagnostics>

  ...

</configuration>

Der ConnectionString ist erfolgreich ersetzt worden.

Der Einsatz in anderen Projekttypen

Leider hat Microsoft die Funktionsweise auf Web-Projekte beschränkt. Andere Projekttypen können die Transformations-Syntax nicht ohne weiteres nutzen. Zu gerne möchte man jedoch auch Console-Anwendungen oder Testprojekte gegen eine andere Build-Konfiguration laufen lassen. Abhilfe schafft hier der manuelle Einbau der Transformation in die Projektdatei.

Die Projektdatei wird bearbeitet, indem man im Kontextmenu des Projektes „Unload“ mit anschließendem „Edit“ aufruft.

Unload Project:

clip_image002

Durch Hinzufügen nachfolgender Zeilen – am besten kurz vor dem schließenden </Project> Tag der Solution – wird die Transformation auf der App.config ausgeführt.

<UsingTask TaskName="TransformXml" 

           AssemblyFile="C:Program FilesMSBuildMicrosoftVisualStudiov10.0WebMicrosoft.Web.Publishing.Tasks.dll"/>

<Target Name="AfterBuild">

  <TransformXml Source="App.config" 

                Condition="Exists('App.$(Configuration).config')" 

                Transform="App.$(Configuration).config" 

                Destination="$(OutDir)$(AssemblyName).dll.config"/>

</Target>

Die Anweisung <UsingTask TaskName=”TransformXml” …> stellt die Transformationsfunktion für das Projekt bereit. Wie man aus dem Pfad des Assembly sehen kann, wird diese aus VS2010 für Web-Projekte entnommen. Im Target “AfterBuild” wird die Transformationsausführung hinterlegt. Hierbei wird zuerst geprüft, ob eine Build-spezifische Konfiguration existiert. Die Destination muss dem Target-Typ angepasst sein – im Beispiel wird eine DLL erzeugt. In der Konsequenz ist der Output eine .dll.config

Generell ist der Einsatz für jede Form von Configuration-Files möglich. Im Web sind deutlich generischere Lösungen verfügbar [2], jedoch greifen diese wesentlich tiefer in das Build-System ein. Persönlich bevorzuge ich den oben beschriebenen Vierzeiler, da dieser in Projekten noch dokumentierbar und verständlich ist. 

Nutzung in TFS

Im TFS Environment werden UnitTests durch eigenständige Rechner, sogenannten BuildAgents, durchgeführt. Benötigt der TFS-Build eigene Konfigurationsparameter, wie beispielsweise ConnectionStrings oder Logging-Einstellungen, können diese auch über die XML-Document-Transform Syntax konfiguriert werden. Aber auch hier gilt es, die eine oder andere Hürde zu überwinden.

Ausgangspunkt der Lösung ist die Erstellung einer eigenständigen Konfiguration für den TFS. Im ConfigurationManager habe ich hierzu eine eigene BuildConfiguration mit dem Namen “TfsBuild” angelegt.

Anlegen einer Build Configuration unter dem Namen “TfsBuild”:

image

Alle Testprojekte erweitere ich – wie vorab beschrieben – um den <TransformXml>-Tasks, so dass die Transformationssyntax angewandt werden kann.  Danach stelle ich eine XDT-Transformation unter dem Namen App.TfsBuild.Config zur Verfügung. Auf einem lokalen Entwicklungsrechner kann ich diese neue Konfiguration in aller Ruhe testen.

Als letzten Schritt, wird der BuildAgent verpflichtet, die neue Konfiguration für seine Builds und UnitTests zu nutzen. Hierzu wird in der BuildDefinition das BuildTarget eingetragen.

Konfiguration des BuildTargets für einen TFS Build:

.clip_image004

Ein Problem bleibt jedoch bestehen. Der TransformationsTask ist Bestandteil von VS2010 und somit leider dem BuildAgent unbekannt. Hier hilft nur die Installation von VS2010 auf dem BuildAgent, der leider damit einem normalen Entwicklerrechner recht ähnlich wird.

Alternativ kann man auch die Datei “C:Program FilesMSBuildMicrosoftVisualStudiov10.0WebMicrosoft.Web.Publishing.Tasks.dll” mit in das Testprojekt aufnehmen. Liegt sie im gleichen Verzeichnis wie das Testprojekt, so genügt es die Referenz auf die DLL wie im folgenden Beispiel leicht umzuschreiben. Das AssemblyFile wird relativ zur Projektdatei angegeben. Die PunktPunkt-Notation für übergeordnete Verzeichnisse funktioniert übrigens nicht.

Nutzung des Tasks aus relativem AssembyFile-Pfad:

<UsingTask TaskName="TransformXml" AssemblyFile="Microsoft.Web.Publishing.Tasks.dll" />
  <Target Name="AfterBuild">
  <TransformXml Source="App.config" 
                Condition="Exists('App.$(Configuration).config')" 
                Transform="App.$(Configuration).config" 
                Destination="$(OutDir)$(AssemblyName).dll.config" />
</Target>
 

Wie man leicht sehen kann, kann die Transformation nicht nur anhand des BuildTargets ausgewählt werden. Mit wenigen Änderungen lassen sich Konfiguration pro Rechner, pro User oder Kombinationen von allem hinterlegen.

Fazit

XML Document Transform ist ein sehr hilfreiches Mittel um Konfigurationen für unterschiedliche Ziel-Targets bereitzustellen. Mit dem beschriebenen 4-Zeiler kann man diese auch in Nicht-Web-Projekten mit wenig Aufwand nutzen. Für den TFS muss der entsprechende Task auf dem BuildAgent verfügbar gemacht werden.

Es gibt deutlich generischere Lösungsansätze [2]. Diese haben aus meiner Sicht den Nachteil, dass sie wesentlich tiefer in die Projektstruktur und den Build-Prozess eingreifen. Der Wartungsaufwand für die Build-Files und die notwendigen Skills für die Entwickler steigen deutlich.

Möchte man lieber XSL für die Transformation nutzen, so gibt es auch hier [3] Lösungen. Die Transformationen scheinen jedoch deutlich größer zu werden.

Links

[1] Die “XML-Document-Transform Syntax”
http://go.microsoft.com/fwlink/?LinkId=125889

[2] Einsatz von XDT Transformation in beliebigen Projekttypen
http://blogs.msdn.com/b/webdevtools/archive/2010/11/17/xdt-web-config-transforms-in-non-web-projects.aspx

[3] Transformation einer App.config mit XSL 
http://mint.litemedia.se/2010/01/29/transforming-an-app-config-file/