msbuild: Eine Anwendung gegen verschiedene .NET Framework- und Third-Party Assembly Versionen kompilieren
Ich habe einen Windows-Dienst, der sich mit CRM-Systemen ab Version 2011 verbinden können muss. Bisher lautete die Strategie für dieses Szenario, die niedrigste Version aller Abhängigkeiten zu wählen, also CRM SDK 5 (2011) und somit .NET Framework 4.0. Das funktionierte auch für CRM 2011, 2013, 2015 und 2016 OnPremises. Lediglich mit CRM 2016 Online gab es schließlich ein Problem, da das Anmeldeverfahren geändert wurde. Die Anmeldung war nur noch unter Verwendung der aktuellen SDK Version 8.x möglich.
Für dieses Problem gab es zwei offensichtliche Lösungsansätze:
- Grundsätzlich gegen die neuste CRM SDK Version kompilieren.
- Einen separaten Branch in der Versionsverwaltung pflegen.
Beide Ansätze hatten jedoch entscheidende Nachteile:
- Das hätte die Anforderung an die .NET Framework-Version auf .NET 4.5.2 erhöht, was nicht gewünscht war.
- Ein zusätzlicher Branch bedeutet zusätzlichen Wartungsaufwand.
Deshalb habe ich einen dritten, eleganteren Lösungsansatz unter Verwendung von msbuild-magic gesucht.
Auch wenn in diesem Beispiel die CRM SDK verwendet wird, lässt sich diese Lösung natürlich auf beliebige Third-Party-Libraries anwenden. Zusätzlich kann auf Grund der verwendeten Compiler-Konstanten auch das Verhalten der Anwendung (z.B. Nutzung neuer bzw. Entfall alter API-Features) beeinflusst werden.
Aufsetzen der Beispiel-Lösung
In der Regel wird man, wenn man vor diesem Problem steht, bereits eine komplexe Lösung haben, daher halte ich es kurz.
Ich verwende für dieses Beispiel eine Konsolenanwendung auf Basis von .NET 4.0. Weiterhin lege ich die verschiedenen Versionen in jeweils eigenen Unterordnern innerhalb eines _lib-Verzeichnisses ab. (Eine Lösung unter Verwendung von nuget-Packages wäre zwar noch eleganter, allerdings habe ich das bisher noch nicht ausprobiert.)
Vorbereitung
Zunächst lege ich eine weitere Build-Konfiguration Release_2016 an und kopiere dabei die Einstellungen der vorhandenen Release-Konfiguration.
Anschließend definiere ich in den Projekt-Eigenschaften im Reiter Builds für die neue Konfiguration eine Compiler-Konstante CRM2016 (hier tut es jeder andere Name natürlich auch). Dieser Schritt ist optional und kann für die bedingte Kompilierung von Code-Teilen verwendet werden, für die reine Kompilierung im Sinne dieses Blog-Eintrags ist das jedoch nicht notwendig.
Für die weiteren Schritte muss die Projektdatei selbst bearbeitet werden. Dazu muss das Projekt zunächst über Project Explorer entladen werden (Rechtsklick à Projekt Entladen) und anschließend im XML-Editor bearbeitet werden (Rechtsklick à Projekt ConsoleApplication_Example.csproj bearbeiten).
Bedingte Auswahl der .NET Framework Version
Um die standardmäßig verwendete .NET Framework-Version 4.0 bei Auswahl der Konfiguration „Release_2016“ zu überschreiben, wird der entsprechenden PropertyGroup das TargetFrameworkVersion Element hinzugefügt und auf die Version 4.5.2 festgelegt:
Jetzt wird bei Auswahl der Konfiguration „Release_2016“ gegen .NET 4.5.2 kompiliert, für alle anderen nach wie vor gegen .NET 4.0.
Bedingte Auswahl der Third-Party-Assembly
Hier wird es etwas komplizierter. Zunächst müssen die Referenzen auf die Third-Party-Assemblies aus der standardmäßigen ItemGroup entfernt werden. Dazu werden die entsprechenden Reference-Elemente gelöscht.
Anschließend wird mit einem MSBuild Choose Element die entsprechende Assembly aus dem jeweiligen Verzeichnis in einer eigenen ItemGroup wieder hinzugefügt. Bei Auswahl der Konfiguration „Release_2016“ sollen die Assemblies aus dem Unterverzeichnis _lib\CRM 2016, andernfalls aus dem Verzeichnis _lib\CRM 2011 verwendet werden.
Das sieht dann folgendermaßen aus:
<figure>
<pre><code tabindex="0" spellcheck="false"><Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!—snip… -->
<Choose>
<When Condition="'$(Configuration)' == 'Release_2016'">
<ItemGroup>
<Reference Include="microsoft.crm.sdk.proxy">
<HintPath>..\..\_lib\CRM 2016\microsoft.crm.sdk.proxy.dll</HintPath>
</Reference>
<Reference Include="microsoft.xrm.sdk">
<HintPath>..\..\_lib\CRM 2016\microsoft.xrm.sdk.dll</HintPath>
</Reference>
</ItemGroup>
</When>
<Otherwise>
<ItemGroup>
<Reference Include="microsoft.crm.sdk.proxy">
<HintPath>..\..\_lib\CRM 2011\microsoft.crm.sdk.proxy.dll</HintPath>
</Reference>
<Reference Include="microsoft.xrm.sdk">
<HintPath>..\..\_lib\CRM 2011\microsoft.xrm.sdk.dll</HintPath>
</Reference>
</ItemGroup>
</Otherwise>
</Choose>
</Project>
</code></pre>
</figure>
Ergebnis
Nach dem erneuten Laden des Projekts wird nun in Abhängigkeit der Build-Konfiguration entweder gegen .NET 4.0 und die CRM 2011 SDK oder gegen .NET 4.5.2 und die CRM 2016 SDK kompiliert.
Da ausschließlich MSBuild-Mechanismen verwendet werden, funktioniert die hier beschriebene Lösung sowohl bei der Kompilierung in Visual Studio selbst als auch über automatisierte Builds, z. B. über den Team-Foundation-Server.