ASP.NET Core 3.1 Web.config Transform for Production

I recently upgraded an application from ASP.NET Core 2.2 to 3.1 around the same time the application got moved to a new server. Side note, don’t make two large changes like that at the same time if you can help it as it always makes tracking down the cause of the issue much harder.¬† The application in question was created using the ASP.NET Core React template initially.

The Error

After the changes above the site started returning the following error.

HTTP Error 500.0 – ANCM In-Process Handler Load Failue

To diagnose the issue I enabled standard out logging by changing the stdoutLogEnabled to true. The following is the line from the application’s web config with the logging enabled.

<aspNetCore processPath="dotnet" arguments=".\YourApplication.dll" stdoutLogEnabled="true" stdoutLogFile=".\logs\stdout">

The Cause

The logging showed that the application was trying to start the React part of the application using the React Development Server, which uses npm. Here is the related code from the Configure function of the Startup class.

app.UseSpa(spa =>
           {
               spa.Options.SourcePath = "ClientApp";

               if (env.IsDevelopment())
               {
                   spa.UseReactDevelopmentServer(npmScript: "start");
               }
           });

As you can see from the highlighted area the Reac Development Server should only be used when the environment is set to development and sure enough, the web.config has environment variable for ASPNETCORE_ENVIRONMENT and the value is Development as you can see in the following example.

<?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="YourApplication.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="InProcess">
        <environmentVariables>
          <environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Development" />
        </environmentVariables>
      </aspNetCore>
    </system.webServer>
  </location>
</configuration>

A Fix

My knee jerk reaction was to find a way to get the value of that variable changed when the application is published in release mode. This helped me find the Microsoft docs for Transform web.config.

For this fix, I used a build configuration base transform. To do this I added a new file named web.Release.config. With this new file present, the transforms in the file will be executed when a release build is run. The following is the transform I used to get ASPNETCORE_ENVIRONMENT set to Production.

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <location>
    <system.webServer>
      <aspNetCore>
        <environmentVariables>
          <environmentVariable xdt:Transform="Replace" xdt:Locator="Match(name)" name="ASPNETCORE_ENVIRONMENT" value="Production" />
        </environmentVariables>
      </aspNetCore>
    </system.webServer>
  </location>
</configuration>

It is important that the structure of your transforms match what is in your actual web.config or the transform won’t be able to locate the element that needs to be transformed which in this case we are looking for an element under configuration/location/system.webServer/aspNetCore/enviromentVariables.

In our example, we are telling the transform we want to replace the element¬† (xdt:Transform=”Replace”) that matches the name (xdt:Locator=”Match(name)”) ASPNETCORE_ENVIRONMENT with the value Production.

Wrapping Up

This is just one small example of what can be done with web.config transforms. The official docs gave me a general high-level idea of what transforms can do, but wasn’t super helpful for what I needed to do. If the docs don’t cover your use case get ready to do a fair amount of searching.

Side note in the application I was referencing above it turns out that the web.config in the published for .NET Core 2.1 didn’t contain a value for ASPNETCORE_ENVIRONMENT which was why it wasn’t an issue before.