.Net, Azure and occasionally gamedev

Asp.Net core sub applications in IIS

2017/09/16

This post is really just a reminder to myself.

I have made the same mistake three times now and each time I had to look up how I fixed it as it is non-obvious.


Problem:

.Net Core (sub)applications on Azure won't start (502.5 server error).

For me this error was difficult to debug because:

Only once I downloaded the build server artifacts and put them in the local IIS as a application + sub application did I begin to understand the issue.

The flaw was a combination of child application config inheritance and the build server transforming the web.config in an unexpected way:

With .Net Core the deep integration with IIS was decoupled (otherwise it wouldn't be able to run on other platforms such as Linux where IIS doesn't exist).

.Net Core only uses IIS as a reverse proxy to serve its requests, while the actual web application is a single executable that uses Kestrel.

If you create a new build definition in VSTS for ".Net Core (using Full .Net Framework) the build step will by default contain command line arguments like so:

/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:DesktopBuildPackageLocation="$(build.artifactstagingdirectory)\WebApp.zip" /p:DeployIisAppPath="Default Web Site"

This will cause your web.config to be modified on deploy (which I wasn't aware of).

If your web config is:

<configuration>
    <system.webServer>
        <!-- some content -->
    </system.webServer>
</configuration>

then it will be transformed to:

<configuration>
    <system.webServer>
        <!-- some content -->
        <handlers>
            <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" />
        </handlers>
      <aspNetCore processPath="dotnet" arguments=".\<web app>.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" />
    </system.webServer>
</configuration>

Note the new aspNetCore section.

With sub applications this is problematic, because by default they inherit all the values of the parent.

In my case, both the parent and child have the aspNetCore handler and the duplicated handler caused the configuration error.

Handlers aren't the only thing that's inherited; anything that's inherited can cause problems and to be honest, I can't think of a single good reason to use inheritance, let alone why it's the default (and can't be turned off globally).

It's possible to use "<clear />" on some collections before adding your own items but it doesn't work on all collections (some use "remove") and most of the time I tend to forget about this feature and don't add the "<clear />".

In order to completely stop inheritance you need to use location tag like so:

<configuration>
    <location path="." inheritInChildApplications="false">
        <system.webServer>
            <!-- some content -->
        </system.webServer>
    </location>
</configuration>

Note that you can't use it on all sections (can't just use it on "configuration" either). So depending on your sections you may have to manually add it for each.

Any content inside the location tag is not inherited down to the child applications.

This of course means you have to apply this tag to the parent application.

The next problem is that the transform is still applied by the build server:

<configuration>
    <location path="." inheritInChildApplications="false">
        <system.webServer>
            <!-- some content -->
        </system.webServer>
    </location>
    <system.webServer>
        <handlers>
            <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" />
        </handlers>
        <aspNetCore processPath="dotnet" arguments=".\<web app>.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" />
    </system.webServer>
</configuration>

The handler being outside the location section means that it will still be inherited down to the child application (the transform isn't aware of the location tag).

So I tried manually specifying the aspNetCore section inside the location tag. I was hoping the transform process would understand that it exists and skip the transform, but no:

<configuration>
    <location path="." inheritInChildApplications="false">
        <system.webServer>
            <!-- some content -->
            <handlers>
                <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" />
            </handlers>
            <aspNetCore processPath="dotnet" arguments=".\<web app>.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" />
        </system.webServer>
    </location>
    <system.webServer>
        <handlers>
            <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" />
        </handlers>
        <aspNetCore processPath="dotnet" arguments=".\<web app>.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" />
    </system.webServer>
</configuration>

The obvious duplication will cause an instant error in the IIS process.

Finally, I found How to: Disable Web.config Transformation.

By adding "<IsTransformWebConfigDisabled>true</IsTransformWebConfigDisabled>" to one of my csproj's PropertyGroups I was able to turn it off.

Now I realize this fix is bad if you are working with classic .Net web applications as they use the transform feature to merge Web.debug.config/Web.release.config depending on build configuration (and perhaps you're using it for staging/etc. environments as well).

If you still require transforms, this github issue might help. nil4 described the process of keeping the transform but specifically preventing the handlers transform.

While I could have pursued this transform as well, I did not, as shortly after finding it I also found the how-to mentioned above (which seemed simpler) and it ended up working for me.

Since I have a Asp.Net Core application, my web.config is merely configuring IIS (which is only used as a reverse proxy).

The actual config is all inside my application (which runs on Kestrel) and all my settings are controlled by appsettings(.Development).json which means I don't need web config transforms at all, but if you need them then what nil4 wrote about might defintely be worth checking out.

How to fix it

My full workaround for sub applications is thus:

  1. Always use "<location path="." inheritInChildApplications="false">" in all applications, this ensures that child application don't unexpectedly inherit stuff

  2. Specify aspNetCore explicitely inside location tag, this is required because of the next step

  3. Set IsTransformWebConfigDisabled to true in csproj, preventing msbuild from adding aspnetcore sections outside the location tag (which in turn would be inherited)

tagged as Azure, .Net Core and IIS