Galdin Raphael
Posted on June 29, 2020
.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";
}
}
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>
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>
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; }
}
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>
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="https://prod.example.com"
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".
Posted on June 29, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
September 25, 2024
August 12, 2024