How to rely on configuration during application bootstrapping?
Tags: c, netcore, configuration, bootstrapping
Published: 2020-03-27 19:54:30

---
title: How to rely on configuration during application bootstrapping?
published: true
date: 2020-03-27 15:54:41 UTC
tags: c#,.net core,configuration,bootstrapping
cover_image: https://blog.kritner.com/2020/03/13/how-to-rely-on-configuration-during-application-bootstrapping/cover.jpg
canonical_url: https://blog.kritner.com/2020/03/13/how-to-rely-on-configuration-during-application-bootstrapping/
---

Working with the IHostBuilder introduced to .netcore has been an overall pleasant experience… until I needed configuration from appsettings for app bootstrapping. Luckily, there’s still a way to do it!

# Introduction

The [docs](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-3.1) on `IHostBuilder` are a good place to start if you’ve not worked with it before - though if you’ve found this post I’d imagine you have and are having the same struggle as me!

The idea of the `IHostBuilder` is the place where you “set up” all the startup bits of the application - the “services” that are a part of it like WebHost, [Orleans](https://blog.kritner.com/2018/10/07/getting-started-with-microsoft-orleans), Logging, IOC configuration, etc.

Here’s an example of what it can look like:

```cs
private static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration((context, builder) =>
        {
            var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
            if (string.IsNullOrWhiteSpace(env))
            {
                throw new Exception("ASPNETCORE_ENVIRONMENT env variable not set.");
            }

            context.HostingEnvironment.EnvironmentName = env;
            
            builder
                .AddJsonFile("appsettings.json", optional: false, reloadOnChange: false)
                .AddJsonFile($"appsettings.{env}.json", optional: false, reloadOnChange: false);
        })
        .ConfigureLogging(builder => { builder.AddConsole(); })
        .ConfigureServices((hostContext, services) =>
        {
            // Add some services
        })
        .ConfigureWebHostDefaults(builder =>
        {
            builder.UseUrls($"https://*:443");
            builder.UseStartup<Startup>();
        });

```

The above should be pretty straight forward, though I am setting some things “again” that the `CreateDefaultBuilder` has already set - like logging and the loading of app settings, for example. What is the issue with the above? Well in my case, I needed a way to be able to change the port the web host was running on, depending on the environment.

There are ways to accomplish this with environment variables, as well as variables passed in the `args` at application run, but I wanted to do it via configuration files. I needed a way to get configuration files “loaded and accessible” during the `ConfigureWebHostDefaults`.

First, we need a class that will represent our configuration, I’ve gone over a bit of this before in [dotnet core console application IOptions configuration](https://blog.kritner.com/2018/09/14/dotnet-core-console-application-ioptions-t-configuration/); see that for a refresher if needed.

The POCO:

```cs
public class MyWebsiteConfig
{
    public int Port { get; set; }
}
```

The appsettings.json:

```json
{ "MyWebsiteConfig": { "Port": 443 }}
```

The appsettings.prod.json:

```json
{ "MyWebsiteConfig": { "Port": 4337 }}
```

In the above, sure we could have just use a “root” level property in our config, but I like doing it this way for getting a strongly typed `IOptions<T>`, as well as demonstrating the fact you could do this with multiple properties via the strongly typed configuration object within your application bootstrapping.

# The problem

So, why can’t we just do what we usually do and either inject the `IOptions<T>` or get the service from our `IServiceProvider`? The problem I was running into is the place where I’d need the `MyWebSiteConfig` - under `ConfigureWebHostDefaults` from the intro, has not yet actually “built” the configuration by ingesting the config files via `ConfigureAppConfiguration` nor set up the services via `ConfigureServices`. This can be confirmed by placing breakpoints in each section (`ConfigureWebHostDefaults`, `ConfigureAppConfiguration`, and `ConfigureServices`) and observing that `ConfigureWebHostDefaults` is the first breakpoint to hit, well before the things we need from configuration are actually loaded.

My initial thoughts were to just create two `HostBuilder`s, one to load the settings I need, get an instance of my `MyWebSiteConfig` and pass it into a new `HostBuilder` that will do (some) of the work again, but this time I’ll have access to what I need.

This _seemed_ to work, aside from the fact that I got a few warnings that stated something to the effect of “don’t do this cuz singleton scoped things will be weird” - I don’t recall the exact warning (or was it error?), but I immediately went on to find another way to do it.

# The solution

Thankfully, I found something promising [IConfigurationBuilder.AddConfiguration](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.configuration.chainedbuilderextensions.addconfiguration?view=dotnet-plat-ext-3.1). This extension method allows for that adding of an `IConfiguration` onto an `IConfigurationBuilder`. What does this mean? It means that I can do _almost_ was I was working toward from “the problem” above, but rather than two separate `HostBuilders`, we’ll use two separate `IConfiguration`s.

So what does our first `IConfiguration` need to be made up of? We know we _at least_ need the app settings files loaded, and a service provider that can return an instance of our `MyWebsiteConfig`. That looks like this:

```cs
private static (string env, IConfigurationRoot configurationRoot, MyWebsiteConfig myWebsiteConfig) BootstrapConfigurationRoot()
{
    var env = GetEnvironmentName();
    var tempConfigBuilder = new ConfigurationBuilder();
    
    tempConfigBuilder
        .AddJsonFile($"appsettings.json", optional: false, reloadOnChange: false)
        .AddJsonFile($"appsettings.{env}.json", optional: false, reloadOnChange: false);

    var configurationRoot = tempConfigBuilder.Build();

    var serviceCollection = new ServiceCollection();
    serviceCollection.Configure<MyWebsiteConfig>(configurationRoot.GetSection(nameof(MyWebsiteConfig)));
    var serviceProvider = serviceCollection.BuildServiceProvider();
    var myWebsiteConfig = serviceProvider.GetService<IOptions<MyWebsiteConfig>>().Value;
    return (env, configurationRoot, myWebsiteConfig);
}

private static string GetEnvironmentName()
{
    var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
    if (string.IsNullOrWhiteSpace(env))
    {
        throw new Exception("ASPNETCORE_ENVIRONMENT env variable not set.");
    }

    return env;
}
```

In the above I’m returning a [named tuple](https://docs.microsoft.com/en-us/dotnet/csharp/tuples#named-and-unnamed-tuples) with a `env`, `configurationRoot`, and `myWebsiteConfig`. Much of this should look familiar:

- Get the environment name
- Create a temporary configuration builder
- Add the settings files to the builder using the environment
- get the `IConfigurationRoot` by building the `tempConfigBuilder`
- Create a new service collection
- Configure the service for `MyWebsiteConfig`
- Build the `IServiceProvider` from the `IServiceCollection`
- Get the service from the `IServiceProvider`
- Return the `env`, `configurationRoot`, and `myWebsiteConfig`

Now that we actually have an instance of `MyWebsiteConfig`, we are able to build our configuration dependent `IHost`:

```cs
private static IHostBuilder CreateHostBuilder(string[] args, string env, IConfigurationRoot configurationRoot, MyWebsiteConfig myWebsiteConfig) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration((context, builder) =>
        {
            context.HostingEnvironment.EnvironmentName = env;
            builder.AddConfiguration(configurationRoot);
        })
        .ConfigureLogging(builder => { builder.AddConsole(); })
        .ConfigureServices((hostContext, services) =>
        {
            // Add some services
        })
        .ConfigureWebHostDefaults(builder =>
        {
            builder.UseUrls($"http://*:{myWebsiteConfig.Port}");
            builder.UseStartup<Startup>();
        });
```

In the above, we’re doing a lot of the same things as before, with just a few additions. First, our method signature is now receiving in addition to `args`, the three items from our named tuple. We’re able to add our existing `configurationRoot` onto the `IHostBuilder`, as well as set the environment. Now, we are able to utilize our `myWebsiteConfig` without having to worry about the ordering of the builder methods of `IHostBuilder`, since we already have our instance of `MyWebsiteConfig` prior to entering the method.

Here’s what it all looks like:

```cs
public static Main(string[] args)
{
    var (env, configurationRoot, myWebsiteConfig) = BootstrapConfigurationRoot();

    CreateHostBuilder(args, env, configurationRoot, myWebsiteConfig).Build().Run();
}

private static (string env, IConfigurationRoot configurationRoot, MyWebsiteConfig myWebsiteConfig) BootstrapConfigurationRoot()
{
    var env = GetEnvironmentName();
    var tempConfigBuilder = new ConfigurationBuilder();
    
    tempConfigBuilder
        .AddJsonFile($"appsettings.json", optional: false, reloadOnChange: false)
        .AddJsonFile($"appsettings.{env}.json", optional: false, reloadOnChange: false);

    var configurationRoot = tempConfigBuilder.Build();

    var serviceCollection = new ServiceCollection();
    serviceCollection.Configure<MyWebsiteConfig>(configurationRoot.GetSection(nameof(MyWebsiteConfig)));
    var serviceProvider = serviceCollection.BuildServiceProvider();
    var myWebsiteConfig = serviceProvider.GetService<IOptions<MyWebsiteConfig>>().Value;
    return (env, configurationRoot, myWebsiteConfig);
}

private static string GetEnvironmentName()
{
    var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
    if (string.IsNullOrWhiteSpace(env))
    {
        throw new Exception("ASPNETCORE_ENVIRONMENT env variable not set.");
    }

    return env;
}

private static IHostBuilder CreateHostBuilder(string[] args, string env, IConfigurationRoot configurationRoot, MyWebsiteConfig myWebsiteConfig) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration((context, builder) =>
        {
            context.HostingEnvironment.EnvironmentName = env;
            builder.AddConfiguration(configurationRoot);
        })
        .ConfigureLogging(builder => { builder.AddConsole(); })
        .ConfigureServices((hostContext, services) =>
        {
            // Add some services
        })
        .ConfigureWebHostDefaults(builder =>
        {
            builder.UseUrls($"http://*:{myWebsiteConfig.Port}");
            builder.UseStartup<Startup>();
        });
```

Running the app and breaking on the `UseUrls` line you can see:

![Port value from config](https://blog.kritner.com/2020/03/13/how-to-rely-on-configuration-during-application-bootstrapping/debugging.png)

Code for this post can be found: [https://github.com/Kritner-Blogs/Kritner.ConfigDuringBootstrapNetCore](https://github.com/Kritner-Blogs/Kritner.ConfigDuringBootstrapNetCore)

## References

- [Getting started with Microsoft Orleans](https://blog.kritner.com/2018/10/07/getting-started-with-microsoft-orleans)
- [dotnet core console application IOptions configuration](https://blog.kritner.com/2018/09/14/dotnet-core-console-application-ioptions-t-configuration/)
- [.NET Generic Host](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-3.1)
- [Named Tuples](https://docs.microsoft.com/en-us/dotnet/csharp/tuples#named-and-unnamed-tuples)
- [IConfigurationBuilder.AddConfiguration](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.configuration.chainedbuilderextensions.addconfiguration?view=dotnet-plat-ext-3.1)
- [GitHub Kritner.ConfigDuringBootstrapNetCore](https://github.com/Kritner-Blogs/Kritner.ConfigDuringBootstrapNetCore)
- Photo by [rimakruciene](https://unsplash.com/photos/gpKe3hmIawg) on [Unsplash](https://unsplash.com/).