77

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 ?

2
  • I am afraid not since you could not get Configuration before initialing Host. Commented Oct 25, 2019 at 6: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 Commented Oct 27, 2019 at 21:37

6 Answers 6

106
+50

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();

    //...
}
Sign up to request clarification or add additional context in comments.

9 Comments

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.
How do you access the configuration from Startup? Call ConfigureConfigurationBuilder again?
Your Startup class can receive an IConfiguration in the constructor via DI
Perfect. I inlined the configuration statement without any issues. +1
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?
|
11

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.

2 Comments

Just to understand your approach: the idea is to leverage ConfigureAppConfiguration to build a throwaway default configuration used for configuring services ?
@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
4

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

4 Comments

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 outside the ConfigureServices you can get the Configuration after the host.Build() method, like this: host.Services.GetRequiredServices<IConfiguration>(). 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<MyTimerService>(); }); Sorry for the text formatting.
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 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.
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.

1 Comment

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

Starting in .NET 7, it's proposed to not use the callback methods anymore and use the linear code. Instead of using ConfigureAppConfiguration, it's recommended to use directly the ConfigurationManager available on the WebApplicationBuilder.

Before:

WebHost.CreateDefaultBuilder(args)
    .ConfigureAppConfiguration((context, builder) =>
    {
        builder.AttachExtraConfiguration();
    });

After:

var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AttachExtraConfiguration();

1 Comment

I am not sure I understand what AttachExtraConfiguration() is. Is that a mock method just to get the point across ? You would have an .AddConfiguration() or equivalent in place of that method ?
-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"

1 Comment

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.