60

Before initializing the application Host I need to read some settings from the application's configuration to setup some other things.

In ASP .NET Core 2.x to read settings before initializing the application Host I used to do the following:

public static void Main(string[] args)
{
    //...

    var configuration = new ConfigurationBuilder()
        .AddEnvironmentVariables()
        .AddCommandLine(args)
        .AddJsonFile("appsettings.json")
        .Build();

    //Do something useful with the configuration...

    var host = WebHost.CreateDefaultBuilder()
        .UseStartup<Startup>()
        .UseConfiguration(configuration)
        .Build();

    //...
}

In ASP .NET Core 3.x WebHost has been deprecated in favor of .NET Generic Host.
.NET Generic Host has only .ConfigureHostConfiguration() and .ConfigureAppConfiguration() that do not take a built configuration as parameter, but instead accept only a delegate used to setup the configuration.

For HTTP workloads you can still use the method .UseConfiguration() has it is exposed by IWebHostBuilder and do essentially the same as before:

public static void Main(string[] args)
{
    //...

    var configuration = new ConfigurationBuilder()
        .AddEnvironmentVariables()
        .AddCommandLine(args)
        .AddJsonFile("appsettings.json")
        .Build();

    //Do something useful with the configuration...

    var host = Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>()
                .UseConfiguration(configuration);
        })
        .Build();

    //...
}

But this only works for HTTP workloads and not for Worker Services.

To get the configuration before setting up the Host I've come up with the following approach:

public static void Main(string[] args)
{
    //...

    var configuration = ConfigureConfigurationBuilder(args)
        .Build();

    //Do something useful with the configuration...

    var host = Host.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration(builder => ConfigureConfigurationBuilder(args, builder))
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        })
        .Build();

    //...
}

public static IConfigurationBuilder ConfigureConfigurationBuilder(string[] args, IConfigurationBuilder configurationBuilder = null)
{
    configurationBuilder ??= new ConfigurationBuilder();

    configurationBuilder
        .AddEnvironmentVariables()
        .AddCommandLine(args)
        .AddJsonFile("appsettings.json");

    return configurationBuilder;
}

Essentially I've wrote a method that setups a ConfigurationBuilder and returns it, so that I can reuse the same configuration. This works in practice, but I build the same configuration twice.

Is there a simpler/more correct way (that works for HTTP workloads and non-HTTP workloads) to build and reuse the configuration before setting up the Host ?

UncleDave
  • 6,872
  • 1
  • 26
  • 44
Gwinn
  • 1,218
  • 1
  • 9
  • 19
  • I am afraid not since you could not get Configuration before initialing Host. – Ryan Oct 25 '19 at 06:59
  • @XingZou The fact is that I'm able to setup the configuration before setting up the host, the only problem is that I cannot reuse that configuration again forcing me to pay the price to intialize the configuration twice – Gwinn Oct 27 '19 at 21:37

5 Answers5

89

You can clear the default sources added by CreateDefaultBuilder then add a pre-built IConfiguration with the AddConfiguration extension method.

public static void Main(string[] args)
{
    //...

    var configuration = new ConfigurationBuilder()
        .AddEnvironmentVariables()
        .AddCommandLine(args)
        .AddJsonFile("appsettings.json")
        .Build();

    //Do something useful with the configuration...

    var host = Host.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration(builder =>
        {
            builder.Sources.Clear();
            builder.AddConfiguration(configuration);
        })
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        })
        .Build();

    //...
}
UncleDave
  • 6,872
  • 1
  • 26
  • 44
  • 3
    Best answer although I had an addition issue because I also used hostContext inside ConfigureAppConfiguration which is not available in the above example. I used hostContext to get HostingEnvironment. – toralux Jan 15 '20 at 23:12
  • How do you access the configuration from `Startup`? Call `ConfigureConfigurationBuilder` again? – Michael Haren Mar 05 '20 at 16:03
  • 3
    Your Startup class can receive an IConfiguration in the constructor via DI – UncleDave Mar 05 '20 at 16:05
  • Perfect. I inlined the `configuration` statement without any issues. +1 – bvj Mar 21 '20 at 08:30
  • For the last year I've had a pre-built configuration file (I needed configuration info for Program.cs) that I built and passed along to CreateDefaultBuilder() as a paramter. It used it fine, but I didn't realize (!!) all this time that what was sent along to Startup.cs was yet another configuration built from defaults during CreateDefaultBuilder. This code was the perfect solution. Thank you! – Wellspring Apr 10 '20 at 20:18
  • 2
    This seems to be correct, but it leads to ignore some settings in ConfigureWebHostDefaults part. For example, webBuilder.UseUrls() totally ignored with such approach. Does anyone know why? – Alek Depler Mar 22 '21 at 13:49
  • @AlekDepler. I have the same problem. – Fulo Lin Apr 09 '21 at 13:36
  • I found the answer [here](https://stackoverflow.com/questions/66125386/asp-net-core-3-1-does-configureappconfiguration-interacts-with-launchsettings-j). – Fulo Lin Apr 09 '21 at 13:57
7

The answer by UncleDave is certainly the best and most correct way to do this, but if you want to use the default configuration without recreating the logic yourself, it is not easy to get access to the IConfiguration and the IWebHostBuilder in the same place.

In order to do this, you can take advantage of the fact that the concrete Configuration is built before other services such as the web host are built. You can use ConfigureAppConfiguration() to access the config then use it to continue building the IWebHostBuilder.

For example:

public static async Task Main(string[] args)
{
    var hostBuilder = Host.CreateDefaultBuilder(args);
    var builder = hostBuilder
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();

            // make sure to put this at the end of this method
            webBuilder.ConfigureAppConfiguration(configBuilder =>
            {
                // compile a temporary configuration instance
                var configuration = configBuilder.Build();

                // Check config values as you would normally
                var myConfig = new MyConfig();
                configuration.Bind("MyConfigSection", myConfig);
                if (myConfig.UseSentryLogging)
                {
                    // configure the web host based on config
                    webBuilder.UseSentry();
                }
            });
        });

    var host = builder.Build();
    await host.RunWithTasksAsync();
}

If you are configuring other services which can affect where configuration is read from, then you may find you need to store a reference to the IWebHostBuilder and call ConfigureAppConfiguration on the IHostBuilder instead. Make sure you call ConfigureAppConfiguration last so that the other configuration setup can take place first and you access it correctly:

public static async Task Main(string[] args)
{
    // we store the reference to the webHostBuilder so we can access it outside of ConfigureWebHost
    IWebHostBuilder _webBuilder = null;

    var hostBuilder = Host.CreateDefaultBuilder(args);
    var builder = hostBuilder
        .ConfigureWebHostDefaults(webBuilder =>
        {
            // store the builder for later
            _webBuilder = webBuilder;
            webBuilder.UseStartup<Startup>();
        })
        .ReadConfigFromSomewhereElse()
        .ConfigureAppConfiguration(configBuilder =>
        {
            // compile a temporary configuration instance
            var configuration = configBuilder.Build();

            // Check config values as you would normally
            var myConfig = new MyConfig();
            configuration.Bind("MyConfigSection", myConfig);
            if (myConfig.UseSentryLogging)
            {
                // configure the web host based on config
                _webBuilder.UseSentry();
            }
        });

    var host = builder.Build();
    await host.RunWithTasksAsync();
}

Note this is a little bit of a hack and so may not work in all cases.

Rhumborl
  • 16,349
  • 4
  • 39
  • 45
  • Just to understand your approach: the idea is to leverage `ConfigureAppConfiguration` to build a throwaway default configuration used for configuring services ? – Gwinn Apr 08 '22 at 12:39
  • 1
    @Gwinn yes that's right. It's a way to hook into the process to get a full configuration before the post-configuration actions like web host setup kick in. In general, building a config should be quick, so not noticeably impact startup time – Rhumborl Apr 08 '22 at 12:48
0

The right question is, do you really need a configuration for your configuration? You can use another file, and bind it directly with entity

configuration.GetSection("entity").Bind(entity);

If I didn't understand what you mean, all I know is that the setup you will need it for dependency injection, or you can't use it, so instead of using this, you can use Extensions for your service collection to do what you want with your configuration, and you get the setup in your container.

Tim
  • 5,435
  • 7
  • 42
  • 62
ousspero
  • 19
  • 4
  • Some values are shared between the application hoste and other parts. A concrete example is a logger configured before the Host so that I can log startup errors. The logger logs _to the application database_ so I want one connection string that will be used by the logger _and_ the application in one configuration file. – Gwinn Oct 29 '19 at 10:45
  • This was all I needed. Thanks op – Chris Marisic Jul 29 '22 at 19:21
0

You can also do something like this. In my own case I needed the "Configuration" in the Program.cs, to pass it to my extension method. The extension method was in "MyExtension.cs" class, and in this class I used the namespace Microsoft.Extensions.DependencyInjection.

In your Program.cs you can use hostContext to retrieve the Configuration:

     using Scheduler.Job;
        
        IHost host = Host.CreateDefaultBuilder(args)
            .ConfigureServices((hostContext, services) =>
            {
                // Retrieve the appsettings configuration
                IConfiguration configuration = hostContext.Configuration;
        
                // Add my extension method and pass the configuration to be binded
                services.AddMyExtensionsMethod(configuration);
        
                services.AddHostedService<MyTimerService>();
            })
            .Build();
        
        await host.RunAsync();

Here the content of MyExtensions.cs, where I will bind the appsettings using a model, and I will add my scoped service:

    // I extended the DI namespace to use AddMyExtensionsMethod in Program.cs
    namespace Microsoft.Extensions.DependencyInjection
    {
        public static class MyExtensions
        {
            // I show only ONE public method to the services in Program.cs, and inside MyExtensions class I add all the private methods with services I need in DI. In this way I add a layer separation        
            public static IServiceCollection AddMyExtensionsMethod(this IServiceCollection services, IConfiguration configuration)
            {
                return services.AddProviders(configuration);
            }
    
            // Here I bind the appsettings configuration with my appsettings ConfigurationModel (a class that represent the appsettings.json object that I need), to pass it in DI
            private static IServiceCollection AddMyProviders(this IServiceCollection services, IConfiguration configuration)
            {
                // Bind the appsettings on my ConfigurationModel
                ConfigurationModel config = configuration.Get<ConfigurationModel>();
    
                // Add my service as scoped
                services.AddScoped<IProviderPluto>(x => new ProviderPluto(config,  x.GetRequiredService<ILogger<PostCallerProvider>>()));
    
                return services;
            }
        }
    }

MarGraz
  • 61
  • 9
  • The point is that you can't access `hostContext` outside of `ConfigureServices`. How would you do it if you needed `hostContext.Configuration` in the `ConfigureAppConfiguration`? – Tanuki Jul 12 '23 at 11:05
  • @Tanuki outside the `ConfigureServices` you can get the Configuration after the `host.Build()` method, like this: `host.Services.GetRequiredServices()`. Otherwise you can use `hostContext` also with `ConfigureAppConfiguration`, like this: Host.CreateDefaultBuilder(args).ConfigureAppConfiguration((hostContext, config) => { IConfiguration configuration = hostContext.Configuration; // Use configuration object here }) .ConfigureServices((hostContext, services) => { services.AddHostedService(); }); Sorry for the text formatting. – MarGraz Jul 12 '23 at 15:34
  • After `host.Build()` is too late, as the point is we need configuration before `host.Build()` is called. About `ConfigureAppConfiguration`, I didnt know that you can use `hostContext` inside it, however I just tried it, and it returns `null` on `hostContext.Configuration.GetSection("settingName").Value`. Seems like configuration is not loaded by this point. If I try the same in `ConfigureServices` it does return correct value. – Tanuki Jul 12 '23 at 15:50
  • 1
    @Tanuki yes, you are right, the configuration seems not already loaded in `ConfigureAppConfiguration`. Is it possible with the hostContext force the loading? Because the advantage to use the `hostContext.Configuration` is that you already have the appsettings mapped, and you just need a model to bind it. It seems that inside the `hostContext.Configuration` is it possible to do this: `config.AddJsonFile(path)`, but more research are necessary. – MarGraz Jul 13 '23 at 09:40
-1

By default ConfigureAppConfiguration() will load appsettings.json according to the documentation Default builder settings.

Alternatively, if you can't access your configuration or you are getting System.NullReferenceException: 'Object reference not set to an instance ofan object.' error, it means the IHostBuilder can't see the json file in it base path.

Check or change the appsettings.json file properties as seen in the image below:

enter image description here

  1. Set "Build Action" to "Content"
  2. Set "Copy to Output Directory" to "Copy if newer"
סטנלי גרונן
  • 2,917
  • 23
  • 46
  • 68
Olakusibe AO
  • 19
  • 1
  • 5
  • 2
    I'm not sure this has anything to do with my question, the problem is not "I can't access configuration" but rather "I have a IConfiguration instance already prepared how can I share it with HostBuiler" ? – Gwinn Jul 17 '20 at 07:25