Custom binders for mapping config string values to types known to the composition root #36540
Description
The ease the Binding portion of this library adds to configuration is very valuable for the projects I'm involved in, but I find a lot of cases where the flexibility of binding seems to be lacking. I might just be approaching problems incorrectly (if so, any help pointing me in the right direction would be nice).
It seems like either the binder will work great because you are working with basic types that are easily bound, or the binder is useless because you need some custom logic, so you are left writing a bunch of manual steps to read out of Configuration (especially around Options).
I know the current Binder code uses the TypeDescriptor.GetConverter(Type type)
method to attempt to do type conversion, but setting converters for types is a pain and doesn't cover all scenarios (e.g. there might be other information we need to get, such as from an IConfiguration, that is not available to us from the TypeConverter implementation). Relying on the TypeDescriptor method also relies heavily on ambient details that aren't always obvious at composition, and are not specific to a particular compositor. Those converters are part of the description of a type, not part of the composition of an application and its configuration/dependencies.
It would be nice if there were methods like this available:
public static IServiceCollection AddConfigurationBinder<T>(
this IServiceCollection services,
Func<string, T> binderFunction)
{
// Later when Bind() is called, ideally it would check for configured binders
// before falling back to a default TypeConverter
}
This would allow us to do things like this:
appsettings.json
{
"ConnectionStrings": {
"SampleConnection": "[connection string goes here]"
},
"SomeLibrarySettings": {
"ShouldDoAThing": true,
"Connection": "SampleConnection"
}
}
SomeLibraryOptions.cs
public class SomeLibraryOptions
{
public bool ShouldDoAThing { get; set; }
// As an example... directions for how reliant code should build out new connections
public Func<IDbConnection> Connection { get; set; }
}
Startup.cs
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.AddConfigurationBinder<Func<IDbConnection>>(configValue => {
// When my options expects to bind a Func<IDbConnection>, use the string in the settings
// to find the matching connection string and make a Func that builds a SqlConnection for it.
var connectionString = Configuration.GetConnectionString(configValue);
return () => new SqlConnection(connectionString);
});
services.Configure<SomeLibraryOptions>(Configuration.GetSection("SomeLibrarySettings"));
}
Does this make sense to do or are there other approaches that exist already (or are being worked on) that are better suited? I know this is a contrived example that would be solved by using the Action version of configure, but when we have common steps to perform on options again and again, I think it might be better to tell the ConfigurationBinder how to handle those type bindings generally vs individually.
Activity