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.
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.
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> -->
AfterBuild outside the comment, and add your
<!-- 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. and
web.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 -
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
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
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.