Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Web.config transformations not working when deploying Blazor WebAssembly application to Production and Staging Environments #35584

Open
BenjaminCharlton opened this issue Aug 21, 2021 · 11 comments
Labels
area-blazor Includes: Blazor, Razor Components enhancement This issue represents an ask for new feature or an enhancement to an existing one feature-blazor-deployment Issues related to deploying Blazor feature-blazor-wasm This issue is related to and / or impacts Blazor WebAssembly
Milestone

Comments

@BenjaminCharlton
Copy link

BenjaminCharlton commented Aug 21, 2021

Describe the bug

I'm not 100% sure if this is a problem with Visual Studio's publishing wizard, or a problem with Blazor, or just something that I did wrong.

I asked on StackOverflow and learned some useful things from the community but we have not solved the problem so far.

Summary
I want to apply a web.config transformation when publishing my Blazor WebAssembly application to my Staging and Production environments via FTP using the Visual Studio Publish Wizard, as described here

I want the transformation to add a custom header containing the blazor-environment variable as documented here

Naturally, I don't want the addition of a web.config file to break my local development environment either (I've noticed that's exactly what happens if you just paste the auto-generated web.config from the production/staging environment into the project root).

No matter what I try, my web.config transformations don't get applied when I publish the site, and publishing takes an hour each time, so progress is slow!

To Reproduce

You can view a minimal reproducible sample here, or follow the steps below.

  1. Create a new Blazor WebAssembly project.
  2. Edit Index.razor so it looks like this:
@page "/"
@inject Microsoft.AspNetCore.Components.WebAssembly.Hosting.IWebAssemblyHostEnvironment Environment

<h1>Hello, world!</h1>

<p>Environment: @Environment.Environment</p>
  1. In configuration manager:
    i) Rename "Debug" to "Development"
    ii) Rename "Release" to "Production"
    ii) Add a new configuration called "Staging", based on "Production"
  2. Right click on your project in Solution Explorer > Add new item > Add a web.config file. The default one is mostly blank, except for the XML declaration, a comment, a pair of configuration tags. Leave it alone because, in my experience, adding anything to it breaks the project when debugging locally. Check that your project still builds and runs locally. It should.
  3. Right click on your project and select "Publish".
  4. Add a new FTP publish profile called "Staging" and save some details of an FTP server you can use as a staging environment. Specify the configuration "Staging" for this profile. Don't publish yet; just save the profile. Mine looks like this:
<?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>temporaryurl1.myhost.net</SiteUrlToLaunchAfterPublish>
    <ExcludeApp_Data>False</ExcludeApp_Data>
    <ProjectGuid>12c14c2e-4d13-4e23-bf64-8e92faf909e9</ProjectGuid>
    <publishUrl>ftp.myhost.net</publishUrl>
    <DeleteExistingFiles>True</DeleteExistingFiles>
    <FtpPassiveMode>True</FtpPassiveMode>
    <FtpSitePath>myproject-stag</FtpSitePath>
    <UserName>user</UserName>
    <_SavePWD>True</_SavePWD>
    <TargetFramework>net5.0</TargetFramework>
    <SelfContained>false</SelfContained>
  </PropertyGroup>
</Project>
  1. Add a new FTP publish profile called "Production" and save some details of an FTP server you can use as a production environment. Specify the configuration "Production" for this profile. Don't publish yet; just save the profile. Mine looks like this:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <EnvironmentName>Production</EnvironmentName>
    <WebPublishMethod>FTP</WebPublishMethod>
    <LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>
    <LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
    <LastUsedPlatform>Any CPU</LastUsedPlatform>
    <SiteUrlToLaunchAfterPublish>temporaryurl2.myhost.net</SiteUrlToLaunchAfterPublish>
    <ExcludeApp_Data>False</ExcludeApp_Data>
    <ProjectGuid>12c14c2e-4d13-4e23-bf64-8e92faf909e9</ProjectGuid>
    <publishUrl>ftp.myhost.net</publishUrl>
    <DeleteExistingFiles>True</DeleteExistingFiles>
    <FtpPassiveMode>True</FtpPassiveMode>
    <FtpSitePath>myproject-stag</FtpSitePath>
    <UserName>user</UserName>
    <_SavePWD>True</_SavePWD>
    <TargetFramework>net5.0</TargetFramework>
    <SelfContained>false</SelfContained>
  </PropertyGroup>
</Project>
  1. Right click on each of the new .pubxml profiles in turn and choose "Add config transform". You should now have a web.Staging.config and a web.Production.config with some boilerplate code.
  2. Paste this into 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="SetAttributes" />
			</customHeaders>
		</httpProtocol>
	</system.webServer>
</configuration>
  1. Paste this into web.Production.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="Production"
					 xdt:Locator="Match(name)"
					 xdt:Transform="SetAttributes" />
			</customHeaders>
		</httpProtocol>
	</system.webServer>
</configuration>
  1. Publish the Blazor application using each of the profiles "Production" and "Staging".

What I hoped would happen

  • The home page of the application in the Production environment should say "Environment: Production" and the home page of the application in the Staging environment should say "Environment: Staging", while in my local development environment, it should still say "Environment: Development".
  • The web.config file published at each site should have been transformed to contain the new customHeader with an appropriate value for blazor-environment. I would expect other transformations to have been applied by the publishing wizard to make Blazor run on IIS (usually, for example, it adds <staticContent><mimeMap> and <httpCompression><dynamicTypes> and other nice stuff like that).
  • The build/publish log in Visual Studio should (I guess) say something about transforming web.config (whether successful or failed).

What actually happened

  • Neither of the published websites has a web.config at all, so running the site produces a 403 error.
  • The build/publish log in VS says nothing about transforming web.config.

Something else I tried that didn't work

I have also tried the above steps but with a complete web.config file in the project root (I got the content off a Blazor WASM application that was published to IIS. This prevents the project running at all in the development environment but it does at least run in Production and Staging. Still, though, no transformations got applied to web.config so the home page says "Environment: Production" in both Production and Staging environments.

@javiercn javiercn added area-blazor Includes: Blazor, Razor Components feature-blazor-wasm This issue is related to and / or impacts Blazor WebAssembly feature-blazor-deployment Issues related to deploying Blazor labels Aug 23, 2021
@pranavkm
Copy link
Contributor

Thanks for your feedback. Blazor WebAssembly currently does not support web.config transform. We'll consider this an enhancement for the backlog. For the time being, we would recommend using a custom web.config. The default contents for the web.config that Blazor adds (using the output you get from publishing the app).

@pranavkm pranavkm added the enhancement This issue represents an ask for new feature or an enhancement to an existing one label Aug 23, 2021
@pranavkm pranavkm added this to the Backlog milestone Aug 23, 2021
@ghost
Copy link

ghost commented Aug 23, 2021

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

@dsoltesz
Copy link

dsoltesz commented Sep 7, 2021

Were you ever able to get a web.config that allows you to still run locally?

@BenjaminCharlton
Copy link
Author

For a stand-alone Blazor WASM project, you should be able to do this at build time (rather than at publish time) if you use the SlowCheetah library as described on the accepted answer on my StackOverflow question.

But, if your project uses Blazor WASM with pre-compilation on the server (as described here), as mine now does, then I still haven't yet found the solution, and would love to know.

@pranavkm pranavkm modified the milestones: Backlog, .NET 7 Planning Oct 19, 2021
@mkArtakMSFT mkArtakMSFT modified the milestones: .NET 7 Planning, Backlog Nov 11, 2021
@ghost
Copy link

ghost commented Nov 11, 2021

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

@MizardX
Copy link

MizardX commented Nov 19, 2021

As a workaround for adding the blazor-environment header. You can add a file called Directory.build.targets in the blazor project, with the content:

<Project>
  <UsingTask
    TaskName="AddBlazorEnvironment"
    TaskFactory="RoslynCodeTaskFactory"
    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll" >
    <ParameterGroup>
      <Template ParameterType="System.String" Required="true" />
      <Target ParameterType="System.String" Required="true" />
      <Environment ParameterType="System.String" Required="true" />
    </ParameterGroup>
    <Task>
      <Reference Include="$(OutputPath)System.Xml.XDocument.dll" />
      <Using Namespace="System.Xml.Linq" />
      <Code Type="Fragment" Language="CS">
        <![CDATA[
        XElement GetOrAdd(XElement elem, string name) {
          var child = elem.Element(name);
          if (child == null) {
            elem.Add(child = new XElement(name));
          }
          return child;
        }

        var doc = XDocument.Load(Template);

        var elem = doc.Root;
        elem = GetOrAdd(elem, "system.webServer");
        elem = GetOrAdd(elem, "httpProtocol");
        elem = GetOrAdd(elem, "customHeaders");

        const string HEADER_NAME = "blazor-environment";
        var add = elem
          .Elements("add")
          .FirstOrDefault(e => e.Attribute("name")?.Value == HEADER_NAME);
        if (add == null) {
          elem.Add(add = new XElement("add",
            new XAttribute("name", HEADER_NAME)
          ));
        }
        add.SetAttributeValue("value", Environment);

        doc.Save(Target);
        ]]>
      </Code>
    </Task>
  </UsingTask>

  <Target Name="TrasformBlazorWebConfigFile"
          AfterTargets="_AddBlazorWebConfigFile">
    <ItemGroup>
      <BlazorTemplateWebConfig Include="@(ResolvedFileToPublish)" Condition=" '%(Filename)%(Extension)' == 'BlazorWasm.web.config' " />
      <BlazorTransformedWebConfig Include="@(BlazorTemplateWebConfig->'$(IntermediateOutputPath)web.config')" />
    </ItemGroup>

    <AddBlazorEnvironment
      Condition="'@(BlazorTemplateWebConfig)' != ''"
      Template="@(BlazorTemplateWebConfig)"
      Target="@(BlazorTransformedWebConfig)"
      Environment="$(EnvironmentName)"
    />

    <ItemGroup Condition="'@(BlazorTemplateWebConfig)' != ''">
      <ResolvedFileToPublish Remove="@(BlazorTemplateWebConfig)" />
      <ResolvedFileToPublish
        Include="@(BlazorTransformedWebConfig)"
        ExcludeFromSingleFile="true"
        CopyToPublishDirectory="PreserveNewest"
        RelativePath="web.config" />
    </ItemGroup>
  </Target>
</Project>

It assumes you don't have an existing web.config file, and intercepts the creation of the default one.

@BenjaminCharlton
Copy link
Author

Holy moley, Markus, this looks perfect!
I haven't yet had time to test it out but I shall give it a try as soon as I can.

Thank you very much for thinking of me!

@ajamrozek
Copy link

ajamrozek commented Aug 18, 2023

@MizardX I tried this solution but not sure how to inject that Environment param
The "AddBlazorEnvironment" task was not given a value for the required parameter "Environment".
Is there any movement on the transformation file approach?

@MizardX
Copy link

MizardX commented Aug 21, 2023

@ajamrozek

@MizardX I tried this solution but not sure how to inject that Environment param The "AddBlazorEnvironment" task was not given a value for the required parameter "Environment". Is there any movement on the transformation file approach?

You can add the parameter -p:EnvironmentName=Development to the arguments when building or publishing. When building in Visual Studio, it should get added automatically.

@mkArtakMSFT mkArtakMSFT modified the milestones: Backlog, BlazorPlanning Nov 5, 2023
@mkArtakMSFT mkArtakMSFT modified the milestones: Planning: WebUI, Backlog Nov 15, 2023
@dotnet dotnet deleted a comment Nov 15, 2023
@Tailslide
Copy link

There should be some kind of publish error if this is not supported. Huge time waster.

@ezelargo
Copy link

ezelargo commented Feb 9, 2024

It is unfortunate that it is not compatible, year 2024 and it still does not work....

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-blazor Includes: Blazor, Razor Components enhancement This issue represents an ask for new feature or an enhancement to an existing one feature-blazor-deployment Issues related to deploying Blazor feature-blazor-wasm This issue is related to and / or impacts Blazor WebAssembly
Projects
None yet
Development

No branches or pull requests

9 participants