Blazor WASM not loading appsettings.{environment}.json in Azure App Services

Introduction

In this blog post, we are going to outline steps that allow developers deploying Blazor WebAssembly projects to workaround a shortcoming related to environment-specific configuration files. Although, typical .NET Core applications are configured to make use of the ASPNETCORE_ENVIRONMENT environment variable, this is not necessarily true for Blazor standalone, as opposed to WebAssembly hosted, projects deployed to Azure App Services. For more information on different hosting options, see ASP.NET Core Blazor hosting models. The result is that settings placed in ‘child’ configuration files like appsettings.Development.json or appsettings.Production.json never get used once deployed to Azure, even if you have properly set the ASPNETCORE_ENVIRONMENT configuration value.

Backstory

After getting everything working nicely on my local machine, I was quite excited to get a project deployed out to the Cloud so it could be shared with our client. Unfortunately, I could not seem to get the Blazor WASM project integrating properly with Identity Server 4. Independent tests of the deployed Identity Server 4 project, as well as another Web API project, were working just fine. So, I suspected / figured out pretty quickly that it was a configuration issue, but it took me a while to nail down exactly what was happening. It probably did not help that several of my past Blazor projects were using Blazor Server SignalR connection circuits instead of WebAssembly. The one prior WASM project I had completed was a relatively simple PWA that did not require complex configuration.

My aha moment was when I came across these two AspNetCore issue submissions:

So, without further ado, let’s first touch on the crux of the problem and then dive right into the (one) solution.

The problem

The crux of the problem is that Blazor WebAssembly standalone applications do not behave in the same way as your typical .NET Core web application when deployed as an Azure App Service. When run locally, a standalone Blazor WebAssembly app works fine because the development server adds the “blazor-environment” header to specify the environment. However, this does not happen once deployed as an Azure App Service. Without the steps below, a standalone app deployed to Azure will read values from appsettings.json and ignore values configured in a file named appsettings.production.json, regardless of the environment. For more information, see ASP.NET Core Blazor environments.

The Solution

One way around this issue is to perform a little trickery during deployment (from Visual Studio) that is definitely not ideal, but works using .NET 5.

1. In Visual Studio, go to project properties | “Build Event” | “Pre-build event command line” and add the following line:

echo window.BlazorEnvironment = '$(ConfigurationName)'; > $(ProjectDir)wwwroot\BlazorEnvironment.js

2. In index.html, BEFORE bootstrapping Blazor with this tag: <script src=”_framework/blazor.webassembly.js”></script>, add the following to make sure the “blazor-environment” header is set to the correct value.

<script src="BlazorEnvironment.js"></script>
<script>
    const originalFetch = fetch;
    fetch = async (url, options) => {
        const response = await originalFetch(url, options);
        return url.endsWith('/blazor.boot.json')
            ? new Response(await response.blob(), { headers: { 'blazor-environment': window.BlazorEnvironment } })
            : response;
    };
</script>

Note that the ‘$(ConfigurationName)’ usage in step 1 typically defaults to “Debug” or “Release” in standard project configurations. In order to ensure calls to HostEnvironment.IsDevelopment() and/or HostEnvironment.IsProduction() continue to function I created two new project configurations, ‘Development’ and ‘Production’, but this only has to be done for the WebAssembly project:

Adding ‘Development’ and ‘Production’ configurations to the WebAssembly project

If you do not do this, then all calls to the IsDevelopment() and IsProduction() methods will always return false. i.e.:

	[Inject]
	public IWebAssemblyHostEnvironment env { get; set; }
...
	if (env.IsDevelopment())
	{
		// Do something in DEV that isn't done in PROD
	}

Next, I renamed the ‘child’ appsettings.json files to ‘appsettings.Development.json’ and ‘appsettings.Production.json’, respectively to keep in sync with my renamed project configurations from step 3 as well as the $(ConfigurationName) variable from step 1. Note, I kept the original appsettings.json file to avoid 404 not found errors, but removed all content from it, except for some empty curly braces:

{ }

I did this because it seemed like the inheritance of values from appsettings.json to, say, appsettings.Production.json was not working correctly. In other words, when I had settings in both files, I believe I was getting the original configuration values from appsettings.json instead of the environment-specific ones. Transferring all DEV/Debug values from appsettings.json to the appsettings.Development.json file seemed to work best.

5. Finally, I would advise that you create a test page to display configuration values once the code is deployed. Sometimes it seemed like changes were not taking effect for me until I did a hard refresh or specifically navigated to “/Index.html” (and then back to just “/”). Here is some sample code to get you started gathering/displaying configuration settings that may be of interest (in this case Identity Server 4 configuration values):

public string OidcConfigurationString
{
	get
	{
		StringBuilder sb = new StringBuilder();
		sb.AppendLine("OidcConfiguration: ");

		var oidcConfigSection = Configuration.GetSection("OidcConfiguration");
		var itemArray = oidcConfigSection.AsEnumerable();
		foreach (var item in itemArray)
		{
			sb.AppendLine($"{item.Key}: {item.Value}");
		}

		return sb.ToString();
	}
}

The output from this can simply be displayed in a razor page as such:

<div style="margin-left:15px;">
	@((MarkupString)OidcConfigurationString)
</div>

As you can guess, this was a big, frustrating, time sink and I have a hard time believing there isn’t a better way. But, until I am enlightened by that better way, this will get me (and hopefully you) past the issue.

Congratulations!

You have just worked around a thorny issue related to deploying Blazor WebAssembly apps as Azure Services…hopefully, if you needed to read this post, it saves you some time!

Leave a Comment

Your email address will not be published. Required fields are marked *