.netazure-devopsazure-pipelinesweb-configweb.config-transform

XML Transforms Not Working in Devops Release Task


I am attempting to use XML transformations in the release task in my Devops config. It seems that only some of the xml settings are getting transformed, and not all of them. My main goal is to get these attributes copied over to my main we.config file:

    <httpCookies httpOnlyCookies="true" requireSSL="true" lockItem="true" />
    <authentication mode="Forms">
      <forms loginUrl="https://app.myapp.com/account/login" name=".APPNAME" timeout="60" requireSSL="true" />
    </authentication>

But these attributes are not being copied/transformed. I've seen similar questions posted, and based on those I have done the following:

I have also looked at the generated "drop" package after the build. I see Web.config, Web.Debug.config, and Web.Release.config in the package as they are supposed to be:

enter image description here

And I have also verified that the xml transformation is turned on:

enter image description here

I am getting some strange results in the release logs. This is why it seems that only some of the xml transforms are not working, and not all of them. This is the Web.Release.config file:

<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <system.web>
    <compilation xdt:Transform="RemoveAttributes(debug)" />
    <httpCookies httpOnlyCookies="true" requireSSL="true" lockItem="true" />
    <authentication mode="Forms">
      <forms loginUrl="https://app.myapp.com/account/login" name=".APPNAME" timeout="60" requireSSL="true" />
    </authentication>
  </system.web>
  <system.webServer>
    <rewrite>
      <rules>
        <clear />
        <rule name="Redirect to https" stopProcessing="true">
          <match url=".*" />
          <conditions>
            <add input="{HTTPS}" pattern="off" ignoreCase="true" />
            <add input="{HTTP_HOST}" pattern="localhost" negate="true" />
            <add input="{REQUEST_URI}" pattern="api/probe" negate="true" />
          </conditions>
          <action type="Redirect" url="https://{HTTP_HOST}{REQUEST_URI}" redirectType="Permanent" appendQueryString="false" />
        </rule>
      </rules>
    </rewrite>
    <httpProtocol>
      <customHeaders>
        <add name="X-Content-Type-Options" value="nosniff" />
      </customHeaders>
    </httpProtocol>
  </system.webServer>
</configuration>

And then in the release logs, it looks like the only transfer that is happening is the "Removing" of the debug attribute: enter image description here

My final web.config (in that section) looks like this:

<system.web>
    <compilation targetFramework="4.8">
        <assemblies>
            <add assembly="System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
            <add assembly="netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" />
        </assemblies>
    </compilation>
    <customErrors mode="RemoteOnly" defaultRedirect="~/Error" />
    <httpRuntime targetFramework="4.5" />
    <authentication mode="Forms">
        <forms loginUrl="https://app.myapp.com/account/login" name=".APPNAME" timeout="60" requireSSL="false" />
    </authentication>
    <roleManager enabled="true" defaultProvider="RingClonePortalRoleProvider">
        <providers>
            <clear />
            <add name="RingClonePortalRoleProvider" type="RingClone.Portal.Security.RingClonePortalRoleProvider" />
        </providers>
    </roleManager>
    <pages>
        <namespaces>
            <add namespace="System.Web.Helpers" />
            <add namespace="System.Web.Mvc" />
            <add namespace="System.Web.Mvc.Ajax" />
            <add namespace="System.Web.Mvc.Html" />
            <add namespace="System.Web.Optimization" />
            <add namespace="System.Web.Routing" />
            <add namespace="System.Web.WebPages" />
            <add namespace="RingClone.Portal" />
        </namespaces>
    </pages>
    <sessionState cookieless="false" regenerateExpiredSessionId="true" mode="Custom" customProvider="SqlSessionStateProviderAsync">
        <providers>
            <!--
        Please change the connection string named "DefaultConnection" to connect to an instance
        of SQL Server which you will use as the data store.
  -->
            <add name="SqlSessionStateProviderAsync" connectionStringName="RingCloneSessionDatabase" type="Microsoft.AspNet.SessionState.SqlSessionStateProviderAsync, Microsoft.AspNet.SessionState.SqlSessionStateProviderAsync, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
        </providers>
    </sessionState>
</system.web>

As you can see, none of the transforms are executing after the <compilation xdt:Transform="RemoveAttributes(debug)" /> are being executed.

My main goal in this is to get the following sections transferred over to the web.config file (from Web.Release.config):

    <httpCookies httpOnlyCookies="true" requireSSL="true" lockItem="true" />
    <authentication mode="Forms">
      <forms loginUrl="https://app.myapp.com/account/login" name=".APPNAME" timeout="60" requireSSL="true" />
    </authentication>

And none of these are being applied. What am I missing here?


Solution

  • When you do transformation, you should keep similar structure between source web.config and web.release.config. Please fix the source web.config structure similar as web.release.config if it doesn't have <configuration>.

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
        <system.web>
            <compilation targetFramework="4.8">
      ....
        </system.web>
    </configuration>
    

    To get the expected sections transferred over to the web.config file, you should use xdt:Transform="Insert" syntax in web.release.config, it inserts section to source.

    <?xml version="1.0"?>
    <configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
      <system.web>
        <httpCookies httpOnlyCookies="true" requireSSL="true" lockItem="true" xdt:Transform="Insert" />
        <authentication mode="Forms">
          <forms loginUrl="https://app.myapp.com/account/login" name=".APPNAME" timeout="60" requireSSL="true" xdt:Transform="Insert" />
        </authentication>
      </system.web>
      <system.webServer>
        <rewrite>
          <rules>
            <clear />
            <rule name="Redirect to https" stopProcessing="true">
              <match url=".*" />
              <conditions>
                <add input="{HTTPS}" pattern="off" ignoreCase="true" />
                <add input="{HTTP_HOST}" pattern="localhost" negate="true" />
                <add input="{REQUEST_URI}" pattern="api/probe" negate="true" />
              </conditions>
              <action type="Redirect" url="https://{HTTP_HOST}{REQUEST_URI}" redirectType="Permanent" appendQueryString="false" />
            </rule>
          </rules>
        </rewrite>
        <httpProtocol>
          <customHeaders>
            <add name="X-Content-Type-Options" value="nosniff" />
          </customHeaders>
        </httpProtocol>
      </system.webServer>
    </configuration>
    

    After transformation, the web.config looks like:

    enter image description here

    enter image description here

    There are two <authentication mode="Forms">(requireSSL="false" and requireSSL="true"), suspect you only want requireSSL="true" item, you can use SetAttributes(requireSSL) to set the attribute.

    So the final web.release.config looks like:

    <?xml version="1.0"?>
    <configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
      <system.web>
        <httpCookies httpOnlyCookies="true" requireSSL="true" lockItem="true" xdt:Transform="Insert" />
        <authentication mode="Forms">
          <forms requireSSL="true" xdt:Transform="SetAttributes(requireSSL)" />
        </authentication>
      </system.web>
      <system.webServer>
        <rewrite>
          <rules>
            <clear />
            <rule name="Redirect to https" stopProcessing="true">
              <match url=".*" />
              <conditions>
                <add input="{HTTPS}" pattern="off" ignoreCase="true" />
                <add input="{HTTP_HOST}" pattern="localhost" negate="true" />
                <add input="{REQUEST_URI}" pattern="api/probe" negate="true" />
              </conditions>
              <action type="Redirect" url="https://{HTTP_HOST}{REQUEST_URI}" redirectType="Permanent" appendQueryString="false" />
            </rule>
          </rules>
        </rewrite>
        <httpProtocol>
          <customHeaders>
            <add name="X-Content-Type-Options" value="nosniff" />
          </customHeaders>
        </httpProtocol>
      </system.webServer>
    </configuration>
    

    enter image description here

    Both values are set in web.config after transformation:

    enter image description here

    You can check the doc Web.config Transformation Syntax for more details.