Repository navigation
Open
Labels
api-suggestionEarly API idea and discussion, it is NOT ready for implementationEarly API idea and discussion, it is NOT ready for implementationarea-Extensions-Configuration
Milestone
Description
Background and motivation
As it currently stands, configuration sources have no way of being asynchronous (Highlighted in #36018).
These new set of types should allow for users to implement asynchronous configuration providers with no breaking changes to the way the existing API's function.
API Proposal
namespace Microsoft.Extensions.Configuration;
public interface IAsyncConfigurationBuilder : IConfigurationBuilder
{
Task<IAsyncConfigurationBuilder> AddAsync(IAsyncConfigurationSource source);
Task<IAsyncConfigurationRoot> BuildAsync();
}
public interface IAsyncConfigurationManager : IConfigurationManager, IAsyncConfigurationBuilder
{
}
public interface IAsyncConfigurationProvider : IConfigurationProvider
{
Task LoadAsync();
}
public interface IAsyncConfigurationRoot : IConfigurationRoot
{
Task ReloadAsync();
}
public interface IAsyncConfigurationSource : IConfigurationSource
{
Task<IAsyncConfigurationProvider> BuildAsync(IAsyncConfigurationBuilder builder);
}
public static class IConfigurationManagerExtensions
{
public static IAsyncConfigurationManager? AsAsyncManager(this IConfigurationManager configurationManager);
}namespace Microsoft.Extensions.Configuration;
public class AsyncConfigurationBuilder : ConfigurationBuilder, IAsyncConfigurationBuilder
{
public Task<IAsyncConfigurationBuilder> AddAsync(IAsyncConfigurationSource source);
public Task<IAsyncConfigurationRoot> BuildAsync();
}
public class AsyncConfigurationProvider : ConfigurationProvider, IAsyncConfigurationProvider
{
public virtual Task LoadAsync();
}
public class AsyncConfigurationRoot : IAsyncConfigurationRoot, IDisposable
{
public AsyncConfigurationRoot(IList<IConfigurationProvider> providers);
public IEnumerable<IConfigurationProvider> Providers;
public string? this[string key];
public IEnumerable<IConfigurationSection> GetChildren();
public IChangeToken GetReloadToken();
public IConfigurationSection GetSection(string key);
public void Reload();
public Task ReloadAsync();
public void Dispose();
}namespace Microsoft.Extensions.Configuration;
-public sealed class ConfigurationManager : IConfigurationManager, IConfigurationRoot, IDisposable
+public sealed class ConfigurationManager : IAsyncConfigurationManager, IAsyncConfigurationRoot, IDisposable
{
+ Task<IAsyncConfigurationBuilder> IAsyncConfigurationBuilder.AddAsync(IAsyncConfigurationSource source);
+ Task<IAsyncConfigurationRoot> IAsyncConfigurationBuilder.BuildAsync();
}API Usage
Using AsyncConfigurationBuilder:
Calling builder.Build(); will only load synchronous sources.
Calling builder.BuildAsync() will load both sync & async sources.
internal class Program
{
static async Task Main(string[] args)
{
var builder = new AsyncConfigurationBuilder();
builder.Add(new MyConfigurationSource());
await builder.AddAsync(new MyAsyncConfigurationSource());
var configuration = await builder.BuildAsync();
}
}
public class MyConfigurationSource : IConfigurationSource
{
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new MyConfigurationProvider();
}
}
public class MyConfigurationProvider : ConfigurationProvider
{
public override void Load()
{
Data["Key"] = "Value";
}
}
public class MyAsyncConfigurationSource : IAsyncConfigurationSource
{
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
throw new NotImplementedException();
}
public async Task<IAsyncConfigurationProvider> BuildAsync(IAsyncConfigurationBuilder builder)
{
await Task.Delay(200); //Simulate some IO work.
return new MyAsyncConfigurationProvider();
}
}
public class MyAsyncConfigurationProvider : AsyncConfigurationProvider
{
public override async Task LoadAsync()
{
await Task.Delay(1000); //Simulate some IO work.
Data["Key"] = "Value";
}
}Using one of the generic host API's:
internal class Program
{
static async Task Main(string[] args)
{
HostApplicationBuilder hostBuilder = Host.CreateApplicationBuilder(args);
hostBuilder.Configuration.Add(new MyConfigurationSource());
var asyncConfigManager = hostBuilder.Configuration.AsAsyncManager();
if (asyncConfigManager is not null)
{
await asyncConfigManager.AddAsync(new MyAsyncConfigurationSource());
}
var host = hostBuilder.Build();
host.Run();
}
}
public class MyConfigurationSource : IConfigurationSource
{
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new MyConfigurationProvider();
}
}
public class MyConfigurationProvider : ConfigurationProvider
{
public override void Load()
{
Data["Key"] = "Value";
}
}
public class MyAsyncConfigurationSource : IAsyncConfigurationSource
{
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
throw new NotImplementedException();
}
public async Task<IAsyncConfigurationProvider> BuildAsync(IAsyncConfigurationBuilder builder)
{
await Task.Delay(200); //Simulate some IO work.
return new MyAsyncConfigurationProvider();
}
}
public class MyAsyncConfigurationProvider : AsyncConfigurationProvider
{
public override async Task LoadAsync()
{
await Task.Delay(1000); //Simulate some IO work.
Data["Key"] = "Value";
}
}Alternative Designs
None.
Reacted by Martin Costello, Vishvaka Ranasinghe, Artyom, cwatson-sidub, Chenfeng Bao, Azhe Kun, Peter-Ivan, kronic, Aay Jay Chan, Damjan Georgiev and 84 more
Reacted by Robert McLaws and Manuel Pfemeter
Reacted by Jihoon Park, neistow, Gustavo Mauricio de Barros and Robert McLaws
Metadata
Metadata
Assignees
Labels
api-suggestionEarly API idea and discussion, it is NOT ready for implementationEarly API idea and discussion, it is NOT ready for implementationarea-Extensions-Configuration
Activity
davidfowl commentedon Dec 3, 2022
The alternative design looks better but did you also look at all of the consumers of the Load method to understand what else would need to be changed to async to make this work in practice? What about the generic hosting APIs?
davidfowl commentedon Dec 20, 2022
The current model is fundamentally incompatible with that @jackbond and if we do that then we should make a different type. The current configuration assumes you can access all of the keys in memory. What you're asking for is asynchronous access to individual keys. If we go that route then we should build a new API that doesn't overlap with the existing configuration provider.
KieranDevvs commentedon Dec 21, 2022
Ok thanks for the feedback, I'm on xmas holiday at the moment, ill revisit this in the new year.
davidfowl commentedon Dec 23, 2022
I don’t understand why you need asynchronous access to individual values and how did you get all of the keys in memory. IMO that’s another interface and a different API completely.
davidfowl commentedon Dec 24, 2022
Right, and I don't have a problem with having a new abstraction for stores like keyvault and azure app configuration. Seems very much like #36035.
If that's the goal of asynchrony, then I would opt for not polluting the existing API and designing a new one.
davidfowl commentedon Dec 24, 2022
Loading everything up front (async or sync) is fine, and why the original API proposal (though I would change the existing interfaces and classes instead of making new ones) is easier to reason about with the existing configuration model.
Anything that requires asynchronous access to values should be a different abstraction. So maybe this
IAsyncConfigurationProviderproposal should be about accessing values asynchronously. Instead of loading all keys up front somehow (Do most of those systems have a cheap way to query all keys? Do we even want that?).I think these providers would be inappropriate for things like configuration binding, which want to know all of the keys (in the entire object hierarchy) before binding. This API would be used directly in the places that need access to individual values at the moment they are queried, without caching from the source.
davidfowl commentedon Dec 24, 2022
Right, to make general abstractions, we need to make sure the pattern holds broadly. I'm making an assumption that enumerating keys without values isn't common, but if we can find places where it is, then those assumptions can change.
davidfowl commentedon Dec 24, 2022
That's ironic because the reason the azure configuration provider and keyvault provider is not ideal today is because the existing abstraction doesn't work well with the existing model😄
davidfowl commentedon Dec 24, 2022
As one of the creators of this abstraction, that's not the case.
FileProviders was never really meant to exist outside of the ASP.NET Core use case of being a readonly file system for web applications. I don't think it has any of the appropriate features for a general purpose file system abstraction (as much as people want it to be).
This doesn't make any sense to me and would make everything less efficient.
Agreed. It's a pain, but everything shouldn't be async or we can just throw performance out the window.
21 remaining items
KieranDevvs commentedon Apr 11, 2025
The issue is point 3.
We need to add
Taskbased methods to theIConfigurationManagerbut doing so would be a breaking change.So the only available option is to create a superseding interface that implements
IConfigurationManagerand adds the async based methods. Lets call this interfaceIAsyncConfigurationManager.We now want to be able to use this in the generic host APIs. If we take a look at
IHostApplicationBuilderwe can see that it defines the configuration via a property of the typeIConfigurationManager. We cant change or add to this interface as it would also be a breaking change.TLDR;
IConfigurationManager, can only inherit and extend.IHostApplicationBuilderexpose their configuration building viaIConfigurationManager.robertmclaws commentedon Apr 12, 2025
Sounds like we just need to introduce an AsyncHost and start over all the way down the Host stack.
Which is probably a good idea, because it's really dumb that HostBuilder and WebHostBuilder don't implement the same interface.
davidfowl commentedon Apr 12, 2025
WebHostBuilder is not getting updates, WebApplicationBuilder is what we’ve been evolving, similarly Host.CreateApplicationBuilder (those implement the same interface )
KieranDevvs commentedon Apr 12, 2025
Id like feedback on this idea:
We move ahead with
IAsyncConfigurationBuilderand the other async interface types for the direct usage of configuration builder and the ability to register, and asynchronously load providers.We also leave the generic host API's as they are (exposing
IConfigurationBuilder).Use case 1 - Using ConfigurationBuilder directly:
Users now instanciate AsyncConfigurationBuilder and load their synchronous and asynchronous providers as expected and build returns an
IAsyncConfigurationRoot(an extension ofConfigurationRootwith the ability to asyncronously reload the data soruces).Use case 2 - Using one of the generic host API's:
Users instanciate the relevant host builder.
They can either call
.Configuration.Add(...)to add their synchronous provider types, or they can cast the host configuration manager toIAsyncConfigurationManagerto access the relevant async API'sThis is what I have in my current PoC:
Granted, I'm fully aware that casting the
ConfigurationManagerisnt discoverable, expected or a nice user experience, but it does solve all of the breaking change issues without having to provide a set of new generic host API's.I'm hoping that seeing as its more of an advanced use case, that if its documented well, it will be sufficient?
Rabadash8820 commentedon Apr 12, 2025
Some thoughts/questions:
ApplicationHistBuilder.Cinfiguration, could we add anIAsyncConfigurationBuilder AsyncConfigurationproperty? The two properties could even point to the same instance potentially. Still a slightly awkward API, but much more discoverable than casting IMO.AsyncConfigurationBuilder.AddAsync()really need to be async? I thought all I/O happened when you actually build the builder; and adding a configuration source would basically just synchronously add it to some private collection.KieranDevvs commentedon Apr 12, 2025
I'm happy to have it as an option and see what others think, I don't feel strongly either way but I think I would prefer the cast as A duplicate property would be confusing. If I were a user discovering them, I personally would assume that the two properties were isolated from each other.Sorry scratch what I said, adding a property to the existing interface isnt possible as it would be a breaking change. It means that anyone who has their own type that implements the interface now has to implement said property. Maybe, we could introduce an extension method on the existing property type to expose the new interface? Something like.AsAsyncManager()AsyncConfigurationBuilderdirectly, its not needed as the data is loaded upon theBuildAsync()call.ConfigurationManager(the type that the generic host API's expose), the manager itself is its own builder and configuration root. This means that when you add a data source, it immediate loads the data upon the.Add/.AddAsynccall. This is done to let the user access the configuration values when configuring other parts of the host before it is built (think DI etc).Example:
Rabadash8820 commentedon Apr 12, 2025
Not necessarily, if we use a default property implementation on the interface. Though idk if the implementation would really be simple enough to be "default". Your extension method idea would work too.
I see, thanks for clarifying. I suppose
ConfigurationManager.Add()could be modified to callAdd()and then wait onBuildAsync(), rather than waiting on an.AddAsync()call. This would not be a breaking API change, just a change to internal logic. Though that would mean potentially repeated calls toAsyncConfigurationBuilder.BuildAsync()and idk if that's allowed.KieranDevvs commentedon Apr 12, 2025
Note that in my PoC, I've also added
IAsyncConfigurationSourceto allow for asynchronous construction of the provider. The await not only loads the data from the source, but it also constructs the source provider asynchronously and adds it to the managers internal sources. Part of this feature extension is to allow users to implement the interface on their own types and asynchronously construct a source if needed.If we forget about
IAsyncConfigurationSourcefor a second. You mean it would look something like this?If so, I think that would lead to confusion as adding an
IConfigurationSourcewould result in available access straight away, but adding anIAsyncConfigurationSourcewould mean your data was missing until you calledBuildAsyncon the configuration.KieranDevvs commentedon Apr 13, 2025
I've updated the proposal to reflect what has been discussed so far.
robertmclaws commentedon Apr 13, 2025
I will look into this, thanks @davidfowl! Maybe my Host-based extensions have all been supporting the wrong interfaces.
KieranDevvs commentedon Apr 15, 2025
If no one has any comments, can this be marked as ready for review?
robertmclaws commentedon Mar 2, 2026
@rosebyte Did you mean to mark this one as .NET 11, or the issue above that you marked as a duplicate?
KieranDevvs commentedon Mar 2, 2026
Its probably that they don't want to mark this issue as planned for .NET 11 as they already have a separate design proposal and don't want to confuse people. I noticed
Microsoft.Testing.Platformalready has aIConfigurationProvider.LoadAsync()so my guess is that this issue gets closed and the .NET team add their own mechanisms (which I have no issues with).Either way, if this gets addressed in .NET 11 then fantastic. 🚀
rosebyte commentedon Mar 3, 2026
Hello, @KieranDevvs is right. We’ll keep this issue, as this is the API proposal, and it’s better to have a single issue for the feature. I can’t promise it will be in net11.0, as it’s already March and priorities can change.