web.config-transform – “my way”

Ich mag die Web.Config-Transforms. Sehr sogar.
Was mich daran stört ist, dass ich eine Build-Konfiguration für jede Stage/jeden Server benötige. Um das zu umgehen (und ggf. vielleicht auch einmal andere xml-Dateien zu transformieren) kann die Transformation auch manuell über einen MSBuild-Task starten.
Im Regelfall habe ich meine Transformationen im “Configs”-Verzeichnis mit der Benennung web.[stage].conf – die lasse ich dann automatisiert beim build & publish erstellen. Dafür habe ich das folgende targets-file, das ich einfach im csproj einbinde:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<SourceWebConfig>$(ProjectDir)\Web.config</SourceWebConfig>
<OutputPath Condition="'$(OutputPath)' == ''">$(ProjectDir)\bin</OutputPath>
<TempWebConfig>$(OutputPath)\Web.Temp.config</TempWebConfig>
<TransformationsBaseDir>$(ProjectDir)\Configs</TransformationsBaseDir>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">11.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets"/>
<ItemGroup>
<WebConfigTransforms Include="$(TransformationsBaseDir)/*.config">
<TransformDest>$(OutputPath)\%(WebConfigTransforms.Filename).config</TransformDest>
</WebConfigTransforms>
</ItemGroup>
<Target Name="TransformAllWebConfigs">
<!-- Does the real transformation of all web-config transforms... -->
<Copy SourceFiles="$(SourceWebConfig)"
DestinationFiles="$(TempWebConfig)" />
<Message Text="transforming %(WebConfigTransforms.Identity) to %(WebConfigTransforms.TransformDest)"
Importance="high"/>
<TransformXml Source="$(TempWebConfig)"
Transform="%(WebConfigTransforms.Identity)"
Destination="%(WebConfigTransforms.TransformDest)"
StackTrace="true" />
<Delete Files="$(TempWebConfig)" />
</Target>
<Target Name="TransformAfterBuild"
AfterTargets="Build">
<!-- after build, build all web.configs, too -->
<CallTarget Targets="TransformAllWebConfigs" />
<Message Text="TransformAllWebConfigs has run after build!"
Importance="high"/>
</Target>
<Target Name="MimicVsTransforms"
AfterTargets="PreTransformWebConfig">
<!-- this should mimic the original VS-behaviour -->
<CallTarget Targets="TransformAllWebConfigs" />
</Target>
<Target Name="CopyTransformedForPublish"
AfterTargets="CopyAllFilesToSingleFolderForPackage">
<!-- "GatherAllFilesToPublish" as AfterTarges seems to work, too. But only when Publish is called from within VS. -->
<Copy SourceFiles="@(WebConfigTransforms->'%(TransformDest)')"
DestinationFolder="$(WPPAllFilesInSingleFolder)" />
</Target>
</Project>

Die Einbindung im projekt-file erfolgt ganz “normal”.

<Import Project="$(ProjectDir)\..\TransformWebConfig.targets" Condition="'$(TransformationsBaseDir)' == ''" />

In einem aktuellen Projekt bestand die Anforderung die Transformationen in Unterverzeichnissen abzulegen (da es noch mehr spezifische Dateien pro Stage gab…). Dafür habe einfach die ItemGruop wie folgt angepasst:

<ItemGroup>
<WebConfigTransforms Include="$(TransformationsBaseDir)\**\web.config">
<StageName>$([System.String]::new('%(RecursiveDir)').TrimEnd('\\'))</StageName>
<TransformDest>$(OutputPath)\web.%(StageName).config</TransformDest>
</WebConfigTransforms>
</ItemGroup>