Thursday, May 18, 2023
Web.config transformation on the fly and custom publish actions
Many times, I’ve seen multiple configuration files managed as a link to an external file (the configSource
attribute in the Web.config
file), and those links changed before publishing a project. I wasn’t satisfied with that practice, so I explored how to transform the Web.config
file during every build, not just the publishing one. Luckily, there is an option but requires editing files manually. In addition to customizing a build, I’ve also learned how to add custom publish actions.
Web.config transformation
If your project doesn’t have the transformation files yet (upgraded solution) or you added new build configurations, open your project in Visual Studio, right-click the existing Web.config
file, and select Add config transform. This will create config files for existing solution configurations (e.g. web.Debug.config
and web.Release.config
).
The main problem with an XML transformation is that it happens on the “web publish” step only, and you can’t have it setup for the build (via Visual Studio IDE, that is).
Doing a little study on the MSBuild project file reveals you can set it up on the build by calling TransformXml
(Microsoft.Web.Publishing.targets) directly, and only by editing the project file.
Now, right-click on the project, select Open folder in file explorer, and edit your .csproj
(assuming it’s a C# project) file.
Find a portion of the file with following:
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
Now move AfterBuild
outside the comment, and add your TransformXml
task:
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
-->
<Target Name="AfterBuild">
<TransformXml Source="Web.Default.config" Transform="$(ProjectConfigTransformFileName)" Destination="web.config" />
</Target>
You can see that I used Web.Default.config
as the source (add Web.Default.config
as a copy of web.config
to the project). In the Web publish process MSBuild uses your web.config
and transforms it to a web.config
in the temp folder - but we want to have web.config
modified right where our project is.
So, this is how my files are organized:
Web.Default.config
- a master file that contains web config default code,Web.XY.config
- transformation files. andweb.config
- this file will always be generated, so don’t modify it.
It can take a while to get used to having web.config
changed on every build, as a single character difference (even blank space) can trigger source code management tools, but this is superior to changing configuration files manually as you can:
- Have your build configuration remembered by the Visual Studio IDE (saved in
.suo
), so several team members can work with different settings automatically, and - Have publish have its own configuration build -
web.Staging.config
, andweb.Production.config
.
Obviously, a NuGet package can write to the config
file and overwrite your generated file settings, so track those changes and manually add them to Web.Default.config
.
You can ignore changes to the file using:
git update-index --assume-unchanged "web.config"
git update-index --no-assume-unchanged "web.config"
Custom publish actions
If you are using publish to FileSystem
like I am, the usual step after the publish is to package the files using 7zip (I know there’s a Publish to package option that does the same thing).
There’s no way to do this using Visual Studio IDE, so again, we must resort to editing files manually. If we open our .pubxml
publish profile file, we can see that it’s an MSBuild file, and we can add our custom code to it.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!-- ... settings ... -->
</PropertyGroup>
<!-- ADDED -->
<Target Name="CustomAfterCopyAllFilesToSingleFolderForPackage">
<PropertyGroup>
<CurrentDate>$([System.DateTime]::Now.ToString(dd-MM-yyyy-hh-mm-ss))</CurrentDate>
<Publish7zDir>$([System.IO.Path]::GetFullPath('$(publishUrl)\'))</Publish7zDir>
</PropertyGroup>
<Message Text="7zipping files..." Importance="high"/>
<Exec ContinueOnError="True" Command=""c:\Program Files\7-Zip\7z.exe" a -r "$(Publish7zDir)\$(CurrentDate).7z" "$(MSBuildProjectDirectory)\$(_PackageTempDir)\*.*"" />
<Message Text="7zipping done." Importance="high"/>
</Target>
<PropertyGroup>
<OnAfterCopyAllFilesToSingleFolderForPackage>
$(OnAfterCopyAllFilesToSingleFolderForPackage);
CustomAfterCopyAllFilesToSingleFolderForPackage;
</OnAfterCopyAllFilesToSingleFolderForPackage>
</PropertyGroup>
</Project>
This simple action executes after OnAfterCopyAllFilesToSingleFolderForPackage
, and that’s the last event we can attach to FileSystem
publish.
The benefit of using web.config
transformation during the build is that you can easily switch between different configurations without worrying about committing unwanted settings that affect other people on the project. Custom publish actions provide a way to program any custom action to be done at the end of the publish process.