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?
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
I have a C# function app written in .NET 6.0 and I want to read a collection of settings from the local.settings.json file. Here is my startup file that I want to read my settings so I can access them later in my application:
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
BindAndRegisterStronglyTypedConfig(builder);
}
private void BindAndRegisterStronglyTypedConfig(IFunctionsHostBuilder builder)
{
var products = new Products();
builder.GetContext().Configuration.Bind(nameof(Products), products);
builder.Services.AddSingleton(products);
}
}
Then in my local.settings.json file I have the products that I want to read:
{
"Values": {
"Products": [
{
"ProductId": "#",
"Title": "#"
},
{
"ProductId": "#",
"Title": "#"
}
]
}
}
The models the values should bind to:
public class Products
{
public readonly List<Product> products = new List<Product>();
}
public class Product
{
public string ProductId { get; set; }
public string Title { get; set; }
}
Anybody understand what I've done wrong, I get no error just a null value for collection?
Check if the below findings help the requirement:
read my local settings so I can access them later in my application:
One of the ways is in the Same Function Trigger Code, you can fetch the Configuration settings using Configuration class. Below is the sample code for it, taken from the SO reference 70576009
public static object GetAppSettings(IConfiguration configuration, ILogger log)
{
string localsetting1 = configuration.GetValue("Setting1");
}
And another workaround is to Check the local.settings.json configuration code should be in clear format with the Parent-child format where child will be in key-value relationship which is similar to your given configuration and that configuration settings fetching process code and the format of local.settings.json is given in the SO#72786915 by the user #SimonPrice.
Your local.settings.json code is same given in the above reference if it is formatted such as:
{
"Values": {
"Products:ProductId": "#",
"Products:Title": "#",
"Products:ProductId2": "#",
"Products:Title2": "#",
}
}
Suppose we have the following config file
{
"TeamRoster:People:0:Name": "Bryan",
"TeamRoster:People:0:City": "Toronto",
"TeamRoster:People:0:Interests": "Code",
"TeamRoster:People:1:Name": "Sebastian",
"TeamRoster:People:1:City": "Vancouver",
"TeamRoster:People:1:ApartmentType": "Condo"
}
And the following POCOs to represent this config:
public class TeamRoster
{
public Person[] People { get; set; }
}
public class Person
{
public string Name { get; set; }
public string City { get; set; }
}
Assuming I'm using a ConfigurationBuilder to read the dotnet core config with something like:
IConfiguration config =
new ConfigurationBuilder()
.AddJsonFile("file.json")
.Build();
var myConfig = config.GetSection("TeamRoster").Get<TeamRoster>();
How can I capture the additional properties that weren't part of the Person object (Interests, ApartmentType)?
My reasoning is beyond the trivial example above. In my model, I might have dozens of additional attributes (eg like polymorphic types) and I'd want to capture them without having to define them in the config object. Ideally, I'd want to do this like [JsonExtendedData] but as far as I understand, even though the data is expressed as Json that's just the source. It's read into a config object and then bound.
Is there a way to customize the .net core binding?
To shed some light on this, IConfigurationRoot within Microsoft.Extensions.Configuration abstracts the configuration data from its underlying configuration sources. Out of the box, configuration data can come from Json, Azure KeyVault, Command Line, Environment Variables, Xml, Memory, Ini files, User secrets and individual files (key per file).
Each of these configuration sources loads their data into a dictionary where hierarchical values are separated using ':'. When you fetch data from a IConfiguration object, it attempts to resolve the value from each data source in reverse order. Thus the last added configuration source has the highest precedence.
The mapping of IConfiguration objects to POCO objects is done using the ConfigurationBinder. While the ConfigurationBinder does support resolution of values using TypeConverters, this scenario is only helpful for mapping individual string values within the config dictionary, not entire objects.
The ConfigurationBinder has native support for binding collections, so in theory the following two approaches can be used for the following config.
Separate Fixed Values from Dynamic Values
Represent the data so that the dynamic data is separate from the static data.
{
"auth": {
"providers": [
{
"name": "clientsecret",
"loginUrl": "https://login.microsoftonline.com/mytenant/oauth2",
"fields": {
"client_id": "xxx",
"client_secret": "yyy"
"resource": "https://management.azure.com",
"grant_type": "client_credentials"
}
},
...
]
}
}
When binding the dynamic fields to a Dictionary<string,string>, the Binder maps fields as Key/Value pairs. The key advantage for this approach is we're able to leverage TypeConverters and type-safety with the fixed fields.
public class AuthProvider
{
// FIXED FIELDS
public string Name { get; set; }
public Uri LoginUrl { get; set; }
// DYNAMIC FIELDS
public Dictionary<string,string> Fields { get; set; }
}
Map all Data as Dynamic
The slightly more hackish route is to describe all the fields in the data as siblings and then bind the entire data as a Dictionary.
{
"auth": {
"providers": [
{
"name": "clientsecret",
"loginurl": "https://somewhere",
"client_id": "xxx",
"client_secret": "yyy",
"scope": "https://url",
"grant_type": "client_credentials"
}
]
}
}
We can simply take advantage of binding directly to a dictionary. Note we use lose the type-converter for the Uri field:
public class AuthProvider : Dictionary<string,string>
{
public string Name => this["name"]
public string LoginUrl => this["loginUrl"]
public Dictionary<string,string> Fields
{
get
{
var propertyNames = GetType().GetProperties().Select(x => x.Name).ToArray();
var keyPairs = this.Where(kvp => !propertyNames.Contains(kvp.Key));
return
new Dictionary<string,string>(
keyPairs,
StringComparer.OrdinalIgnoreCase);
}
}
}
To configure my services, I need to set singleton by instance like this:
IConfigurationSection settingsSection = Configuration.GetSection("AppSettings");
MySettings settings = new MySettings();
settingsSection.Bind(settings);
// something to do with the instance, so I need it here
services.Configure<MySettings>(settings);
When I inspect settingsSection items, I see its keys are all prefixed with Appsettings: (ie: AppSettings:AppId, AppSettings:AppUrl, ...).
Thus, binding is not done and my settings object isn't initialized.
Is there a way to prevent this prefix, as I already know I'm in that section?
Here is how the appsettings.json looks like:
{
"AppSettings": {
"AppId": 3540350,
"AppUrl": "http://localhost:542",
"AppEnabled": true,
...
}
}
Here is how MySettings class looks like:
public class MySettings
{
public int AppId { get; set;}
public string AppUrl { get; set;}
public bool AppEnabled { get; set;}
...
}
EDIT
I stupidly kept an old naming convention that can't be deserialized (I guess):
{
"AppSettings": {
"App.Id": 3540350,
"App.Url": "http://localhost:542",
"App.Enabled": true,
...
}
}
That question can be closed.
If you want settings to be singleton I guess you could use the extension method Get of IConfigurationSection, something like this:
var settings = configuration.GetSection("AppSettings").Get<MySettings>();
services.AddSingleton(settings);
Image of sample web project running:
I'm trying to load some settings from the appsettings file and I've got a small issue with the way lists are loaded when using the options pattern. Suppose I've got the following classes (for loading the settings):
public class Application {
public string Name { get; set; } = "";
public IEnumerable<string> Roles { get; set; } = new[] {""};
public Application ToApplicationWithoutPass() =>
new Application {
Name = Name,
Username = Username,
Roles = Roles.ToList()
};
}
public class Applications {
public IEnumerable<Application> AppList { get; set; } = new List<Application>();
}
And here is what the settings that are defined on the appsetings file look like:
"Applications": {
"AppList": [
{
"Name": "SraWebuserAdmin",
"Roles": [ "SraEntitiesWriters", "SraEntitiesReaders", "SraEntitiesLoginAccess" ]
},
...
Here are the entries from the DI setup which is done on the ConfigureServices method:
services.Configure<Applications>(options => Configuration.GetSection("Applications").Bind(options));
services.AddScoped<IApplicationAccessVerifier, ApplicationAccessVerifier>();
And, finally, here's the constructor of the ApplicationAccessVerifier class:
public ApplicationAccessVerifier(IOptionsSnapshot<Applications> applicationOptions) {
_applicationOptions = applicationOptions;
}
Now, the question: if I don't initialize the AppList property,
public class Applications {
public IEnumerable<Application> AppList { get; set; }
}
then the settings are loaded correctly.
However, if I initialized it like I've shown (making sure the filed wrapper by the property is initialized with an empty list), then the settings won't be copied to the AppList.
I find this strange since simple properties (ex.: Name on the Application class) aren't affected by the same issue.
Can anyone tell me why this happens or point me to an official documentation about it?
I am using standard configuration pattern for ASP.NET Core applications and I can not bind configuration to my class as it has construtor with parameters.
In appsettings.json I included desired config:
"MyServiceConfig": {
"Identity": {
"Version": "1.0",
"ComplicatedUri": {
"Scheme": "http",
"Authority": "localhost",
"Path": "SuperService"
}
}
},
My config class and it's dependencies look like that:
public class MyServiceConfig
{
public MyIdentity Identity { get; set; }
}
public class MyIdentity
{
public string IdentityName { get; set; }
public string Version { get; set; }
public MyComplicatedUri ComplicatedProperty { get; set; }
public MyIdentity(string version, MyComplicatedUri complicatedProperty)
{
Version = version;
ComplicatedProperty = complicatedProperty;
IdentityName = complicatedProperty.Path;
}
}
public class MyComplicatedUri
{
public string Scheme { get; set; }
public string Authority { get; set; }
public string Path { get; set; }
}
I have already tried code like that:
private MyServiceConfig GetMyConfig(IConfiguration configuration)
{
var config = new MyServiceConfig();
configuration.GetSection("MyServiceConfig").Bind(config);
return config;
}
It throws exception:
'Cannot create instance of type 'MyIdentity' because it is missing
a public parameterless constructor.'
That behaviour can make sense in some cases but in that particular one not so much. Mappings could be straightforward - by property names which have public setters or by constructor parameter names.
Another idea would be adding converter in AddJsonOptions in Startup class for my types - IConfiguration.Bind could infer how to construct it but I also tried that with no success.
Have you encoutered similar problems and found some reasonable solution to that?
Edit: Adding parameterless constructor will work of course, but sometimes I need to deal with some classes from external packages I'd like to use as parts of my config class so let's assume we can not modify them. I'd like to avoid adding new types for mapping only as well. Ideally I'd like to force ASP.NET Core engine to use existing constructor with parameters and by parameter name map with json properties - which currently is not working.
You should just add a default constructor in MyIdentity class.
.bind() binds the configuration into the object using the default constructor.
So, add the required default constructor in your MyIdentity class and it will be fine.
public MyIdentity(){}
Also, you can use Options.
In ConfigureServices, add the following:
services.AddOptions();
services.ConfigureOptions<MyServiceConfig>();
and then use dependency injection to initialize it.
In addition, use your own JsonConverter