Build-time configuration for Blazor WebAssembly Apps using MS Build properties
Tags: blazor, dotnet
Published: 2020-06-29 10:49:10

---
title: Build-time configuration for Blazor WebAssembly Apps using MS Build properties
published: true
date: 2020-06-28 12:04:44 UTC
tags: blazor,dotnet
canonical_url: https://galdin.dev/blog/build-time-configuration-for-blazor-webassembly-apps-using-ms-build-attributes/
---

.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](https://github.com/gldraphael/blazor-build-time-configuration)

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

```csharp
[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:

```xml
<ItemGroup>
  <AssemblyAttribute Include="WasmApp.BuildConfigurationAttribute">
    <_Parameter1>$(BaseUrl)</_Parameter1>
    <_Parameter2>$(BuildDate)</_Parameter2>
  </AssemblyAttribute>
</ItemGroup>
```

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

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

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:

```csharp
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; }
}
```

And now we can use it as usual:

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

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

```

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

![Screenshot with defaults](https://galdin.dev/content/images/2020/06/1.png)

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:

```sh
dotnet build -p:BaseUrl="https://prod.example.com"
dotnet run --no-build
```

![Screenshot with overriden values](https://galdin.dev/content/images/2020/06/2.png)

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