1

My web.config transformations are not having any effect when I publish my site to my third party host from Visual Studio 2019's publish wizard.

I have a Blazor WebAssembly application and three environments: Development, Staging and Production.

In MyProject\Properties\PublishProfiles I have a .pubxml file each for Staging and Production on a third party hosting provider. (I don't need a .pubxml for Development because I plan on only running that on my local development PC.

Here is what Staging.pubxml looks like:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <PropertyGroup>
        <EnvironmentName>Staging</EnvironmentName>
        <WebPublishMethod>FTP</WebPublishMethod>
        <LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>
        <LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
        <LastUsedPlatform>Any CPU</LastUsedPlatform>
        <SiteUrlToLaunchAfterPublish>staging.myproject.com</SiteUrlToLaunchAfterPublish>
        <ExcludeApp_Data>False</ExcludeApp_Data>
        <ProjectGuid>12c14c2e-4d13-4e23-bf64-8e92faf909e9</ProjectGuid>
        <publishUrl>ftp.hostingprovider.net</publishUrl>
        <DeleteExistingFiles>False</DeleteExistingFiles>
        <FtpPassiveMode>True</FtpPassiveMode>
        <FtpSitePath>myproject-blazor-stag</FtpSitePath>
        <UserName>myloginname</UserName>
        <_SavePWD>True</_SavePWD>
    </PropertyGroup>
</Project>

I would like to insert the following in the <configuration> element of my web.config file whenever the project is published using the Staging.pubxml profile:

<system.webServer>
   <httpProtocol>
      <customHeaders>
         <add name="blazor-environment" value="Staging" />
      </customHeaders>
   </httpProtocol>
</system.webServer>

https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/environments?view=aspnetcore-5.0#set-the-environment-via-header

I should be able to do it using a web.config transformation.

https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/transform-webconfig?view=aspnetcore-5.0

So, in my project root directory I added an XML tranformation called Web.Staging.config Here's the content:

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
   <location>
      <system.webServer xdt:Transform="InsertIfMissing">
         <httpProtocol xdt:Transform="InsertIfMissing">
            <customHeaders xdt:Transform="InsertIfMissing">
               <add name="blazor-environment" value="Staging"
                    xdt:Locator="Match(name)"
                    xdt:Transform="InsertIfMissing" />
            </customHeaders>
         </httpProtocol>
      </system.webServer>
   </location>
</configuration>

I was expecting that, when I publish my site using the Staging.pubxml profile, this Web.Staging.config transformation would add the missing section to the web.config file deployed to the third party host. Then, when I load up the Blazor application in a browser, it should be using the settings for the Staging environment. Unfortunately, the transformation is never applied to the web.config file. I downloaded the file to check, and nothing had been added. Thefore, the Blazor app runs using the settings of the Production environment instead of Staging.

Can anybody see what I did wrong, please?

EDIT: There is now a minimal reproducible sample on my GitHub issue.

benjamin
  • 1,364
  • 1
  • 14
  • 26
  • I think that after `system.webserver` you need to add `` before ``. Try it. – Nicola Biada Aug 19 '21 at 22:32
  • Hi @NicolaBiada! Thank you for the suggestion! I have given it a try but it produced invalid XML according to that schema. is not valid inside – benjamin Aug 20 '21 at 08:54
  • 1
    Yes, my bad, sorry! Look at this thread: https://stackoverflow.com/questions/15102834/web-config-transforms-insert-if-not-exists. Someone says that you need `xdt:Locator="Match(path)` on every `InsertIfMissing` to work as expected. – Nicola Biada Aug 20 '21 at 09:05
  • The answer you linked to has a really useful tool for testing transformation files so I'm going to mention it here http://webconfigtransformationtester.apphb.com/ It helped me ascertain that I should not have a tag (thank you). But it's still not working. – benjamin Aug 21 '21 at 10:00
  • I also learned (here https://learn.microsoft.com/en-us/aspnet/web-forms/overview/deployment/visual-studio-web-deployment/web-config-transformations) that you can right-click on any transformation .config file in VS and go to Preview Transform to see what the result of the transformation would be. This is really helpful and proves that I would now get exactly what I want *IF* the transformation got applied. But it's not getting applied for some reason. – benjamin Aug 21 '21 at 10:02
  • 1
    Ok Benjamin, later I'll give it a try. – Nicola Biada Aug 21 '21 at 14:07
  • Wow, that would be great if you have time, thank you, Nicola. If you do, there is a minimal reproducible sample on my GitHub issue, linked here: https://github.com/dotnet/aspnetcore/issues/35584 – benjamin Aug 22 '21 at 05:02
  • 1
    After a LOT of attempts I can confirm that transformation works as expected in an MVC project but __NOT__ in an Webassembly project. The only possibility is to use the `true` parameters but it only copy the web.config from the project to the publishing folder, without transformations. – Nicola Biada Aug 22 '21 at 18:02
  • Wow, @NicolaBiada, thank you for persevering! I really appreciate it. That's not good news - I was hoping it would be my fault so I could fix it! So I guess it's some kind of bug unique to deploying Blazor WASM applications. – benjamin Aug 22 '21 at 18:07
  • 1
    I think that a possible workaround in the meantime could be a transformation during build. But after a lot attempts I haven't found any real working solution. If you are interested I can share this process. – Nicola Biada Aug 22 '21 at 22:21
  • That could work. I guess you mean like a SlowCheetah sort of solution, where whatever build configuration you select is used to choose a web.{BuildConfiguration}.config and transform a template of web.config file at build time? – benjamin Aug 23 '21 at 04:23
  • 1
    Yes, it is. I'm trying to use only the Vs tools without any additional Nugent/tools but it seems the system copy the file without any transformation. – Nicola Biada Aug 23 '21 at 04:49
  • 1
    Solution in the answer Benjamin. Evaluate to shorten the title with something more "direct" to the problem. – Nicola Biada Aug 23 '21 at 05:55
  • Thank you for your help, Nicola. Like you, I also try to use as few extra tools as possible, but this will solve the problem until (hopefully) a fix is deployed in Blazor. – benjamin Aug 23 '21 at 09:15

1 Answers1

0

You're transformation is possible via SlowCheetah NuGet package.
This package is under Microsoft Dotnet Foundation, so I can confirm that is currently supported on the latest stable version of Visual Studio projects, including Blazor WASM.

Solution

TheProject.csproj

<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
    <PublishIISAssets>true</PublishIISAssets>
    <Configurations>Debug;Release;Staging</Configurations>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="5.0.9" />
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="5.0.9" PrivateAssets="all" />
    <PackageReference Include="Microsoft.VisualStudio.SlowCheetah" Version="3.2.26">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="System.Net.Http.Json" Version="5.0.0" />
  </ItemGroup>

  <ItemGroup>
    <None Include="web.config">
      <TransformOnBuild>True</TransformOnBuild>
    </None>
    <None Include="web.Release.config">
      <DependentUpon>web.config</DependentUpon>
      <IsTransformFile>True</IsTransformFile>
    </None>
    <None Include="web.Staging.config">
      <DependentUpon>web.config</DependentUpon>
      <IsTransformFile>True</IsTransformFile>
    </None>
  </ItemGroup>
</Project>

web.config

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <staticContent>
      <remove fileExtension=".blat" />
      <remove fileExtension=".dat" />
      <remove fileExtension=".dll" />
      <remove fileExtension=".json" />
      <remove fileExtension=".wasm" />
      <remove fileExtension=".woff" />
      <remove fileExtension=".woff2" />
      <mimeMap fileExtension=".blat" mimeType="application/octet-stream" />
      <mimeMap fileExtension=".dll" mimeType="application/octet-stream" />
      <mimeMap fileExtension=".dat" mimeType="application/octet-stream" />
      <mimeMap fileExtension=".json" mimeType="application/json" />
      <mimeMap fileExtension=".wasm" mimeType="application/wasm" />
      <mimeMap fileExtension=".woff" mimeType="application/font-woff" />
      <mimeMap fileExtension=".woff2" mimeType="application/font-woff" />
    </staticContent>
    <httpCompression>
      <dynamicTypes>
        <add mimeType="application/octet-stream" enabled="true" />
        <add mimeType="application/wasm" enabled="true" />
      </dynamicTypes>
    </httpCompression>
    <rewrite>
      <rules>
        <rule name="Serve subdir">
          <match url=".*" />
          <action type="Rewrite" url="wwwroot\{R:0}" />
        </rule>
        <rule name="SPA fallback routing" stopProcessing="true">
          <match url=".*" />
          <conditions logicalGrouping="MatchAll">
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
          </conditions>
          <action type="Rewrite" url="wwwroot\" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

web.Release.config

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
    <system.webServer xdt:Transform="InsertIfMissing">
      <httpProtocol xdt:Transform="InsertIfMissing">
        <customHeaders xdt:Transform="InsertIfMissing">
          <add name="blazor-environment" value="Prod"
               xdt:Locator="Match(name)"
               xdt:Transform="InsertIfMissing" />
        </customHeaders>
      </httpProtocol>
    </system.webServer>
</configuration>

web.Staging.config

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <system.webServer xdt:Transform="InsertIfMissing">
    <httpProtocol xdt:Transform="InsertIfMissing">
      <customHeaders xdt:Transform="InsertIfMissing">
        <add name="blazor-environment" value="Staging"
             xdt:Locator="Match(name)"
             xdt:Transform="InsertIfMissing" />
      </customHeaders>
    </httpProtocol>
  </system.webServer>
</configuration>

I've created two Publishing Profile, one for Release and one for Staging and both work as expected.

The web.config will be available in the bin\Staging\net5.0\browser-wasm\publish folder if you publish to a folder.

Note

Thanks to SlowCheetah your transformations will be applied in the Build process, not only in the Publishing process.

So you'll can find your transformed web.config in the output folder, like bin\Staging\net5.0

Nicola Biada
  • 2,325
  • 1
  • 8
  • 22