Web.config transformation on the fly and custom publish actions
Development | Igor Drazic

Web.config transformation on the fly and custom publish actions

Friday, Sep 8, 2017 • 3 min read
Multiple configurations in VS projects are important for big projects. Having to change and maintain configuration files can be a nightmare if done manually, and it's prone to errors. This is where Web.config transformation feature comes in.

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. 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 - web.Staging.config, and web.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="&quot;c:\Program Files\7-Zip\7z.exe&quot; a -r &quot;$(Publish7zDir)\$(CurrentDate).7z&quot; &quot;$(MSBuildProjectDirectory)\$(_PackageTempDir)\*.*&quot;" />
    
    <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.