Skip to content

Commit e2c7f44

Browse files
committed
Fixes configuration provider timing
Ensures `DotNetConsole.Configuration` properly reflects all configuration providers, including those added via `ConfigureAppConfiguration`, when accessed early in the application lifecycle. This is achieved by building the configuration root only once upon first access and rebuilding it when new configuration delegates are added. Previously, `DotNetConsole.Configuration` was built too early, before custom `ConfigureAppConfiguration` delegates could be applied, leading to missing configuration sources. This change modifies the `DotNetConsoleBuilder` to create an `IConfigurationBuilder` initially and then builds the full `IConfigurationRoot` on-demand, incorporating all accumulated `ConfigureAppConfiguration` delegates.
1 parent 3264ce6 commit e2c7f44

3 files changed

Lines changed: 77 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
- `ConfigureAppConfiguration` method to `DotNetConsoleBuilder` (re-)enabling custom configuration providers
1313

14+
## Fixed
15+
16+
- Fixed configuration provider timing: `DotNetConsole.Configuration` now properly reflects all configuration providers (including those added via `ConfigureAppConfiguration`) when accessed early in the application lifecycle.
17+
1418
## [5.0.0] - 2025-01-27
1519

1620
### Added

Neolution.DotNet.Console/DotNetConsoleBuilder.cs

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,21 @@ public class DotNetConsoleBuilder
3838
/// </summary>
3939
private readonly List<Action<HostBuilderContext, IConfigurationBuilder>> configurationDelegates = new();
4040

41+
/// <summary>
42+
/// The initial configuration builder used to create dynamic configuration
43+
/// </summary>
44+
private readonly IConfigurationBuilder configurationBuilder;
45+
46+
/// <summary>
47+
/// The host builder context used for dynamic configuration building
48+
/// </summary>
49+
private readonly HostBuilderContext hostBuilderContext;
50+
51+
/// <summary>
52+
/// The built configuration root - built once when first accessed
53+
/// </summary>
54+
private IConfigurationRoot? builtConfiguration;
55+
4156
/// <summary>
4257
/// Run only to check dependencies.
4358
/// </summary>
@@ -49,13 +64,20 @@ public class DotNetConsoleBuilder
4964
/// <param name="hostBuilder">The host builder.</param>
5065
/// <param name="commandLineParserResult">The command line parser result.</param>
5166
/// <param name="environment">The environment.</param>
52-
/// <param name="configuration">The configuration.</param>
53-
public DotNetConsoleBuilder(IHostBuilder hostBuilder, ParserResult<object> commandLineParserResult, IHostEnvironment environment, IConfiguration configuration)
67+
/// <param name="configurationBuilder">The initial configuration builder.</param>
68+
internal DotNetConsoleBuilder(IHostBuilder hostBuilder, ParserResult<object> commandLineParserResult, IHostEnvironment environment, IConfigurationBuilder configurationBuilder)
5469
{
70+
ArgumentNullException.ThrowIfNull(configurationBuilder);
71+
5572
this.hostBuilder = hostBuilder;
5673
this.commandLineParserResult = commandLineParserResult;
5774
this.Environment = environment;
58-
this.Configuration = configuration;
75+
this.configurationBuilder = configurationBuilder;
76+
this.hostBuilderContext = new HostBuilderContext(new Dictionary<object, object>())
77+
{
78+
HostingEnvironment = environment,
79+
Configuration = configurationBuilder.Build(),
80+
};
5981
}
6082

6183
/// <summary>
@@ -66,7 +88,35 @@ public DotNetConsoleBuilder(IHostBuilder hostBuilder, ParserResult<object> comma
6688
/// <summary>
6789
/// Gets a collection of configuration providers for the application to compose. This is useful for adding new configuration sources and providers.
6890
/// </summary>
69-
public IConfiguration Configuration { get; }
91+
public IConfiguration Configuration
92+
{
93+
get
94+
{
95+
// Build the configuration once when first accessed, similar to Microsoft's approach
96+
if (this.builtConfiguration == null)
97+
{
98+
// Create a new configuration builder based on the initial one
99+
var configBuilder = new ConfigurationBuilder();
100+
101+
// Add all sources from the initial configuration builder
102+
foreach (var source in this.configurationBuilder.Sources)
103+
{
104+
configBuilder.Add(source);
105+
}
106+
107+
// Apply all configuration delegates that have been added via ConfigureAppConfiguration
108+
foreach (var configureDelegate in this.configurationDelegates)
109+
{
110+
configureDelegate(this.hostBuilderContext, configBuilder);
111+
}
112+
113+
// Build and store the configuration root
114+
this.builtConfiguration = configBuilder.Build();
115+
}
116+
117+
return this.builtConfiguration;
118+
}
119+
}
70120

71121
/// <summary>
72122
/// Gets the collection of services for the application to compose. This is useful for adding user provided or framework provided services.
@@ -83,6 +133,10 @@ public DotNetConsoleBuilder ConfigureAppConfiguration(Action<HostBuilderContext,
83133
ArgumentNullException.ThrowIfNull(configureDelegate);
84134

85135
this.configurationDelegates.Add(configureDelegate);
136+
137+
// Reset the built configuration so it gets rebuilt on next access
138+
this.builtConfiguration = null;
139+
86140
return this;
87141
}
88142

@@ -129,7 +183,7 @@ internal static DotNetConsoleBuilder CreateBuilderInternal(Assembly assembly, Ty
129183
// Create configuration and environment instances that are only valid before the host is built.
130184
// We want to expose these as read-only properties in the DotNetConsoleBuilder.
131185
var environment = DotNetConsoleDefaults.CreateConsoleEnvironment(args);
132-
var configuration = DotNetConsoleDefaults.CreateConsoleConfiguration(assembly, args, environment);
186+
var configBuilder = DotNetConsoleDefaults.CreateConsoleConfigurationBuilder(assembly, args, environment);
133187

134188
// Create a HostBuilder
135189
var builder = Host.CreateDefaultBuilder(args)
@@ -153,7 +207,7 @@ internal static DotNetConsoleBuilder CreateBuilderInternal(Assembly assembly, Ty
153207
.ToArray();
154208

155209
var parsedArguments = Parser.Default.ParseArguments(args, verbTypes);
156-
var consoleBuilder = new DotNetConsoleBuilder(builder, parsedArguments, environment, configuration);
210+
var consoleBuilder = new DotNetConsoleBuilder(builder, parsedArguments, environment, configBuilder);
157211

158212
// Apply any custom configuration delegates that will be added later via ConfigureAppConfiguration
159213
builder.ConfigureAppConfiguration((context, configBuilder) =>

Neolution.DotNet.Console/DotNetConsoleDefaults.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,18 @@ internal static DotNetConsoleEnvironment CreateConsoleEnvironment(string[] args)
4444
/// <param name="environment">The environment.</param>
4545
/// <returns>The <see cref="IConfiguration" />.</returns>
4646
internal static IConfiguration CreateConsoleConfiguration(Assembly assembly, string[] args, IHostEnvironment environment)
47+
{
48+
return CreateConsoleConfigurationBuilder(assembly, args, environment).Build();
49+
}
50+
51+
/// <summary>
52+
/// Creates the console configuration builder (without building it).
53+
/// </summary>
54+
/// <param name="assembly">The assembly.</param>
55+
/// <param name="args">The arguments.</param>
56+
/// <param name="environment">The environment.</param>
57+
/// <returns>The <see cref="IConfigurationBuilder" />.</returns>
58+
internal static IConfigurationBuilder CreateConsoleConfigurationBuilder(Assembly assembly, string[] args, IHostEnvironment environment)
4759
{
4860
var configurationBuilder = new ConfigurationBuilder()
4961
.SetBasePath(environment.ContentRootPath)
@@ -62,7 +74,7 @@ internal static IConfiguration CreateConsoleConfiguration(Assembly assembly, str
6274

6375
configurationBuilder.AddEnvironmentVariables();
6476

65-
return configurationBuilder.Build();
77+
return configurationBuilder;
6678
}
6779

6880
/// <summary>

0 commit comments

Comments
 (0)