Home > Enterprise >  How to cross-bind single IOptions POCO to multiple configuration providers?
How to cross-bind single IOptions POCO to multiple configuration providers?

Time:12-03

I'm new to .net core and I have a problem with using multiple configuration providers for a single POCO. I'm using .net core, not asp.net core.

I tried to simplify the classes for clarity, please ignore any compilation errors. The model for my configuration is as follows:

public class Configurations
{
  public EnvironmentConfig EnvironmentConfig {get; set;}
  public ServerConfig ServerConfig {get; set;}
}

public class EnvironmentConfig
{
  public string EnvironmentStr {get; set;}
}

public class ServerConfig
{
  public string Address {get; set;}
  public string Username {get; set;}
  public string Password {get; set;}
}

I have two providers - appsettings.json and a database. The database is used with other services, so it's not possible to alter the data easily (it can be considered read-only). The appsettings.json file has the following hierarchy:

{
  "EnvironmentConfig": {
    "EnvironmentStr": "dev"
  },
  "ServerConfig": {
    "Address": "https://example.com"
    // the other properties are not here, they're kept only in the database
  }
}

The database doesn't have hierarchy and provides only the missing properties (I'll use JSON again for consistency):

{
  "Username": "user"
  "Password": "password"
}

When I try to get a IOptionsMonitor<Configurations> object, only the Address property is populated inside ServerConfig, like it's only reading from appsettings.json (username and password are at the root level, so it's not binding them correctly):

var configs = host.Services.GetService<IOptionsMonitor<Configurations>>().CurrentValue;

{
  "EnvironmentConfig": {
    "EnvironmentStr": "dev"
  },
  "ServerConfig": {
    "Address": "https://example.com"
    "Username": null,
    "Password": null
  }
}

And when I try to get a IOptionsMonitor<ServerConfig> object, it binds only the data from the database:

var serverConfig = host.Services.GetService<IOptionsMonitor<ServerConfig>>().CurrentValue;

{
  "Address": null,
  "Username": "user",
  "Password": "password"
}

It looks like I'm not binding it correctly. I tried multiple ways, but it didn't work:

public static IServiceCollection AddConfiguration(this IServiceCollection services, IConfiguration configuration)
{
  services
    .Configure<ServerConfig>(configuration) // the db properties are also at root level
    .Configure<Configurations>(configuration);
  return serviceCollection;
}
public static IServiceCollection AddConfiguration(this IServiceCollection services, IConfiguration configuration)
{
  services
    .AddOptions<ServerConfig>()
    .Bind(configuration.GetSection("ServerConfig");
  services
    .AddOptions<Configurations>()
    .Bind(configuration);
  return serviceCollection;
}

Could someone explain how to bind it correctly, regardless of the difference in hierarchy, so ServerConfig has all properties populated when inside Configurations?

CodePudding user response:

I am assuming the database is accessed as a custom configuration provider

Update the keys coming from the database to match the desired hierarchy

Database:

"ServerConfig:Username" -> "user"
"ServerConfig:Password" -> "password"

so the configuration framework knows what you are referring to when it merges all the configuration from the multiple configuration providers.

Extract the settings from the database and prepends the hierarchy to the keys when loading configuration.

Simplified example:

public class ServerConfigProvider : ConfigurationProvider {
    public ServerConfigProvider (Action<DbContextOptionsBuilder> optionsAction) {
        OptionsAction = optionsAction;
    }

    Action<DbContextOptionsBuilder> OptionsAction { get; }

    public override void Load() {
        var builder = new DbContextOptionsBuilder<MyEFConfigurationContext>();

        OptionsAction(builder);

        using (var dbContext = new MyEFConfigurationContext(builder.Options)) {
            Data = dbContext.MySettingsTable.ToDictionary(c => $"ServerConfig:{c.Key}", c => c.Value);
        }
    }    
}

Reference Custom configuration provider

  • Related