DEV Community

Galdin Raphael
Galdin Raphael

Posted on • Originally published at galdin.dev on

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 https://xyz.com, the request goes to https://xyz.com/appsettings.json. 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. I'm gonna try and address that with this post.

Source Code: https://github.com/gldraphael/blazor-build-time-configuration

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";
    }
}
Enter fullscreen mode Exit fullscreen mode

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

<ItemGroup>
  <AssemblyAttribute Include="WasmApp.BuildConfigurationAttribute">
    <_Parameter1>$(BaseUrl)</_Parameter1>
    <_Parameter2>$(BuildDate)</_Parameter2>
  </AssemblyAttribute>
</ItemGroup>
Enter fullscreen mode Exit fullscreen mode

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

<PropertyGroup>
  <BaseUrl>https://example.org</BaseUrl>
  <BuildDate>$([System.DateTime]::UtcNow.ToString("dd MMM, yyyy"))</BuildDate>
</PropertyGroup>
Enter fullscreen mode Exit fullscreen mode

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);
    builder.RootComponents.Add<App>("app");

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

    // Register the configuration 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; }
}
Enter fullscreen mode Exit fullscreen mode

And now we can use it as usual:

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

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

Enter fullscreen mode Exit fullscreen mode

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

Screenshot with defaults

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="https://prod.example.com"
dotnet run --no-build
Enter fullscreen mode Exit fullscreen mode

Screenshot with overriden values

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".

Top comments (0)