Back to top

Avoiding repeated Web.config transformations in Azure Devops pipelines

I recently experienced an issue where the way I was doing web.config transformations was resulting in duplicated lines in the transformed file, after it was pumped through an Azure DevOps pipeline build and release.

In the build pipeline, as defined in azure-pipelines.yaml, I have a step to publish the web project to a folder, which then later gets zipped up and deployed.

 task: DotNetCoreCLI@2
  displayName: 'Core Project: Publish MyProject.WebCore project'
  inputs:
    command: 'publish'
    arguments: '--configuration Release --output myproject.webcore\publish_output'
    projects: 'MyProject.WebCore/MyProject.WebCore.csproj'
    publishWebProjects: false
    zipAfterPublish: false
    modifyOutputPath: false

The configuration is being set to Release.

When this step is run, it will GENERATE a Web.config file if one does not already exist. If you have no Web.config or Web.XXXX.config files, then it will most likely look something like this, defining the hosting model as “inprocess”:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <location path="." inheritInChildApplications="false">
    <system.webServer>
      <handlers>
        <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
      </handlers>
      <aspNetCore processPath="dotnet" arguments=".\YouLi.UI.WebCore.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="inprocess" />
    </system.WebServer>
  </location>
</configuration>

Now in my case, I’m going to host the production version of this site on Azure, and I wanted some extra congiguration added, for rewrite rules (they could be configured in code in the new .NET Core project, but I’m going through a migration from .NET Framework and I just wanted to move across all my existing rules for now).

So I created a Web.Release.config file, in which I set up a “insert” transformation, like this:

<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <location>
    <system.webServer>

      <rewrite xdt:Transform="Insert">
        <rules>
          <rule name="About redirect" stopProcessing="true">
            <match url="^about$" />
            <conditions>
              <add input="{HTTP_HOST}" pattern="(.*)mysite\.io" />
            </conditions>
            <action type="Redirect" url="https://mysite.com/about" redirectType="Temporary" />
          </rule>

          <!-- Etc for all my other rules -->

        </rules>
      </rewrite>
    </system.webServer>
  </location>
</configuration>

Don’t worry about the rewrite rules themslves, just note that I had it set to do an “Insert” type of transformation.

As a result of this, if I ran dotnet publish as per above with the configuration set to “Release”, I would end up with a transformed Web.config file, that also included the rewrite section.

So I thought I was winning!

Sad face: downtime as a result of deployment

The next step was for my release pipeline to run.

I am running all this in production on an Azure App Service. I have an older .NET Framework app running in another app service, and I’m using YARP as a revesre proxy to allow me to migration from Framework to Core see my other articles about this.

Now, since I was creating this .NET Core project deployment very similarly to my old .NET Framework one, when I added in my new .NET Core app service deploy step, I copied the setting across under “File Transforms & Variable Substitution Options” for XML Transform – setting it to “true”.

alt text

The problem is, by the time the file gets to the point of being deployed to my production environment, the Web.config file has already had that transformation applied to it, so I was ending up with two rewrite sections:

<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <location>
    <system.webServer>

      <rewrite>
        <rules>
          <rule name="About redirect" stopProcessing="true">
            <match url="^about$" />
            <conditions>
              <add input="{HTTP_HOST}" pattern="(.*)mysite\.io" />
            </conditions>
            <action type="Redirect" url="https://mysite.com/about" redirectType="Temporary" />
          </rule>

          <!-- Etc for all my other rules -->

        </rules>
      </rewrite>
      <rewrite>
        <rules>

          <rule name="About redirect" stopProcessing="true">
            <match url="^about$" />
            <conditions>
              <add input="{HTTP_HOST}" pattern="(.*)mysite\.io" />
            </conditions>
            <action type="Redirect" url="https://mysite.com/about" redirectType="Temporary" />
          </rule>

          <!-- Etc for all my other rules -->

        </rules>
      </rewrite>
    </system.webServer>
  </location>
</configuration>

And naturally that was causing the site to fall over.

The solution was simple: when doing the deploy to production, do NOT check the option for “XML Transform”.

But what about for test enviornments along the way?

For my test environments along the way (e.g. “staging”), I may still want to run a transformation (although in .NET Core land, I am obviously trying to move away from using web.config at all - so for now it really is only the rewrite section that I have to worry about). So I tried creating a Web..config file that does the removal of the rewrite section:

<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <location>
    <system.webServer>
      <rewrite xdt:Transform="Remove">
      </rewrite>
    </system.webServer>
  </location>
</configuration>

The problem is, in the Azure Pipeline, the abovementioned “XML Transform” option will do a transformation with Web.Release.config and THEN do the Web..config transform. So it STILL ends up with a doubling up of the `rewrite` section beacuse of the Release transformation being run twice (once in the build and then once in the release), and then my Environment-specific transformation runs once, so that "Remove" only removes the first copy of it.

In my case, I’m not too worried about leaving the rewrite section in these test environments, because of the types of rewrites I’m doing, but this will depend on what you have in your web.config.

My solution fr now is that I’m just going to turn off the XML Transform on all the environment deploys, and not have any transformation for any environments or configurations other than Release.

There’s another option that I could explore if I really needed special transformations to run on those test environments: using Web.Production.config instead of Web.Release.config for my production environment specific transformations. That could work, but again I’m not too worried about this for now. If you’re interested in doing this but not sure how to, let me know and I can addd it in - it’s how I’ve done it in the past when using other deploy tools that did transformations a little differently.