Configuration in Blazor Client (WASM)

I've been working with Blazor Client (WASM) a fair amount recently and a couple of days ago someone asked me about how to load configuration from a file. I'd never had the need for that type of configuration before but it got me curious about what was possible.

After a little searching I discovered that configuration for Blazor WASM is typically being loaded through an HTTP GET request to a remote resource. That's a completely valid and viable solution but I was specifically curious about loading configuration directly from a file in the Blazor client project, e.g. appsettings.json.

Goal

Load a json configuration file directly from a Blazor WASM client using typical .NET Core practices for working with configuration.

Project Setup

I started by creating the Blazor WebAssembly App BlazorWasmConfig using the template in Visual Studio 2019 (note: I'd previously installed all required dependencies as documented in Getting started in ASP.NET Core Blazor). Next, I removed everything from the BlazorWasmConfig project I wouldn't need. This left me with the single page Index.razor which I'd use to display an object loaded from configuration.

My starting point.

I added the file appsettings.json.

{
  "GalaxyStuff": {
    "GalaxyCluster": "Virgo Supercluster",
    "GalaxyName": "Milky Way",
    "StarCount": 300000000000
  }
}

I defined the class GalaxyInfo to map configuration.

public class GalaxyInfo  
{
    public string GalaxyCluster { get; set; }
    public string GalaxyName { get; set; }
    public long StarCount { get; set; }

    public override string ToString()
    {
        return $"Galaxy: {GalaxyName}, Cluster: {GalaxyCluster}, Stars: {StarCount}";
    }
}

Finally, I installed two NuGet packages for configuration.

Install-Package Microsoft.Extensions.Configuration.Binder  
Install-Package Microsoft.Extensions.Configuration.Json  


Implementation

Creation of a configuration object in a .NET Core application typically looks something like this.

var config = new ConfigurationBuilder()  
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json")
                .Build();

This tells the runtime to look for the file appsettings.json in the current directory and use it as a source for configuration. This simple pattern works well in .NET Core applications everywhere but utterly breaks down in Blazor WASM. The previous code compiles and executes without exceptions but when running in the browser Directory.GetCurrentDirectory() simply returns "/" and there are no files. No matter how you include appsettings.json in the Blazor client project the above method will never find it. This type of file IO simply does not apply to life in the browser.

The secret sauce to reading from a file included in the Blazor client project is including that file as an Embedded resource. This can be done in Visual Studio by setting the Build Action property of the file to Embedded resource and by setting the Copy to Output Directory property to Copy always (or Copy if newer). Alternately, you can specify both settings directly in the project file itself.

<ItemGroup>  
    <EmbeddedResource Include="appsettings.json">
        <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </EmbeddedResource>
</ItemGroup>

Once this is done you can read the file's contents as a Stream. The extension method AddJsonStream makes it easy to add this steam read to the configuration pipeline.

string fileName = "BlazorWasmConfig.appsettings.json";  
var stream = Assembly.GetExecutingAssembly()  
                     .GetManifestResourceStream(fileName)

var config = new ConfigurationBuilder()  
                    .AddJsonStream(stream)
                    .Build();

Note that this method is almost identical to the previous except that instead of loading appsettings.json from a on-disk file using AddJsonFile we are loading it through a stream from the embedded resource using AddJsonStream.

Using the previously defined config object we can then inject configuration into application components as needed. Illustrated here is a Singleton instance of the GalaxyInfo class initialized with data from configuration.

builder.Services.AddTransient(_ =>  
{ 
    return config.GetSection("GalaxyStuff")
                 .Get<GalaxyInfo>(); 
});

In Index.razor page we receive an instance of GalaxyInfo through the dependency injection pipeline shown above and display the ToString() of that object.

@page "/"
@inject GalaxyInfo GalaxyInfo

<h4>@nameof(GalaxyInfo) loaded as config from appsettings.json embedded resource</h4>  
@GalaxyInfo?.ToString()


Results

Conclusions

I was fairly surprised how easy this was to implement. I'm not sure I see a strong need for including a specific configuration file in a Blazor client (or any other web client) but considering how smoothly it went I'll definitely put that in my tool bag.

References