Build-time configuration for Blazor WebAssembly Apps using MS Build properties

.NET Core in general has a nice configuration API that I've been fond of since .NET Core 1.x. But since Blazor WebAssembly apps run completely on the client, the configuration works a little differently that one might expect.

The way configuration using appsettings.json works is that, a HTTP request is sent to a appsettings.json file. So if the app is available at, the request goes to You can also use another file-name or path if you want to.

But if you're just trying to do something like set the Web API's Base URL, a HTTP request on startup can feel like an overkill. This post shall try and address that.

Source Code:

First, we create a custom Attribute to hold our configuration:

[AttributeUsage(AttributeTargets.Assembly, Inherited = false)]
sealed class BuildConfigurationAttribute : Attribute
    public string? BaseUrl { get; }
    public string BuildDate { get; }

    public BuildConfigurationAttribute(string baseUrl, string buildDate)
        BaseUrl = string.IsNullOrWhiteSpace(baseUrl) ? null : baseUrl;
        BuildDate = buildDate ?? "n/a";

Next, we apply this Attribute to the Assembly by passing MS Build properties in the .csproj file:

  <AssemblyAttribute Include="WasmApp.BuildConfigurationAttribute">

We could, optionally, set default values for MS Build properties in the same file, like so:

  <BuildDate>$([System.DateTime]::UtcNow.ToString("dd MMM, yyyy"))</BuildDate>

Next, we access the ApiConfigurationAttribute from Main() to configure it with DI, so that it's accessible to the rest of the application in the usual way:

public static async Task Main(string[] args)
    var builder = WebAssemblyHostBuilder.CreateDefault(args);

    // Get an instance of the attribute applied to the assembly
    var apiConfig = Assembly.GetAssembly(typeof(Program)).GetCustomAttribute<BuildConfigurationAttribute>();

    // Build a config options and register it with DI
    var baseUrl = new Uri(apiConfig.BaseUrl ?? builder.HostEnvironment.BaseAddress);
    builder.Services.Configure<AppOptions>(o =>
        o.BaseUrl = baseUrl;
        o.BuildDate = apiConfig.BuildDate;

    // Set the BaseUrl on the HttpClient
    builder.Services.AddTransient(sp => new HttpClient { BaseAddress = baseUrl });

    await builder.Build().RunAsync();

// AppOptions is defined as
internal class AppOptions
    public Uri BaseUrl { get; set; }
    public string BuildDate { get; set; }

And now we can use it as usual:

@page "/"
@inject IOptions<AppOptions> options

    <strong>Base URL:</strong> @options.Value.BaseUrl
    <strong>Last Build:</strong> @options.Value.BuildDate

That's it. dotnet run or running it within Visual Studio will use the dev-environment-friendly default values:

Now all you need to do to set (or override) the MS Build properties (BaseUrl in this case) in the other environments is to use the -p: or /p: switch:

dotnet build -p:BaseUrl=""
dotnet run --no-build

We now have a nice mechanism to use different API Base URLs for different environments, and have environment specific build-time configuration without the need to make a HTTP request.

I think the decision of making a HTTP request to the appsettings.json file was the right one. Because to me a "configurable" app is one that reads configuration at runtime. Build time configuration is like hardcoding the strings — well, different strings — for each environment in our case. So if I were to use docker, I'd now need to maintain an image for each environment if I choose to use "build time configuration".